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 {
|
dependencies {
|
||||||
|
testCompile 'junit:junit:4.12'
|
||||||
compile 'com.android.support:appcompat-v7:24.1.1'
|
compile 'com.android.support:appcompat-v7:24.1.1'
|
||||||
compile 'com.android.support:support-v4:24.1.1'
|
compile 'com.android.support:support-v4:24.1.1'
|
||||||
compile 'com.android.support:design: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);
|
assertTrue(extractor.getUploadDate().length() > 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void testGetChannelUrl() throws ParsingException {
|
||||||
|
assertTrue(extractor.getChannelUrl().length() > 0);
|
||||||
|
}
|
||||||
|
|
||||||
public void testGetThumbnailUrl() throws ParsingException {
|
public void testGetThumbnailUrl() throws ParsingException {
|
||||||
assertTrue(extractor.getThumbnailUrl(),
|
assertTrue(extractor.getThumbnailUrl(),
|
||||||
extractor.getThumbnailUrl().contains(HTTPS));
|
extractor.getThumbnailUrl().contains(HTTPS));
|
||||||
|
|
|
@ -16,10 +16,10 @@
|
||||||
android:logo="@mipmap/ic_launcher"
|
android:logo="@mipmap/ic_launcher"
|
||||||
android:theme="@style/AppTheme"
|
android:theme="@style/AppTheme"
|
||||||
tools:ignore="AllowBackup">
|
tools:ignore="AllowBackup">
|
||||||
|
|
||||||
<activity
|
<activity
|
||||||
android:name=".VideoItemListActivity"
|
android:name=".MainActivity"
|
||||||
android:label="@string/app_name"
|
android:label="@string/app_name">
|
||||||
android:theme="@style/SplashScreenTheme">
|
|
||||||
<intent-filter>
|
<intent-filter>
|
||||||
<action android:name="android.intent.action.MAIN" />
|
<action android:name="android.intent.action.MAIN" />
|
||||||
|
|
||||||
|
@ -27,12 +27,12 @@
|
||||||
</intent-filter>
|
</intent-filter>
|
||||||
</activity>
|
</activity>
|
||||||
<activity
|
<activity
|
||||||
android:name=".VideoItemDetailActivity"
|
android:name=".detail.VideoItemDetailActivity"
|
||||||
android:label="@string/title_videoitem_detail"
|
android:label="@string/title_videoitem_detail"
|
||||||
android:theme="@style/AppTheme">
|
android:theme="@style/AppTheme">
|
||||||
<meta-data
|
<meta-data
|
||||||
android:name="android.support.PARENT_ACTIVITY"
|
android:name="android.support.PARENT_ACTIVITY"
|
||||||
android:value=".VideoItemListActivity" />
|
android:value=".MainActivity" />
|
||||||
|
|
||||||
<intent-filter>
|
<intent-filter>
|
||||||
<action android:name="android.intent.action.VIEW" />
|
<action android:name="android.intent.action.VIEW" />
|
||||||
|
@ -81,10 +81,12 @@
|
||||||
android:configChanges="orientation|keyboardHidden|screenSize"
|
android:configChanges="orientation|keyboardHidden|screenSize"
|
||||||
android:theme="@style/VideoPlayerTheme"
|
android:theme="@style/VideoPlayerTheme"
|
||||||
tools:ignore="UnusedAttribute" />
|
tools:ignore="UnusedAttribute" />
|
||||||
|
|
||||||
<service
|
<service
|
||||||
android:name=".player.BackgroundPlayer"
|
android:name=".player.BackgroundPlayer"
|
||||||
android:exported="false"
|
android:exported="false"
|
||||||
android:label="@string/background_player_name" />
|
android:label="@string/background_player_name" />
|
||||||
|
|
||||||
<activity
|
<activity
|
||||||
android:name=".player.ExoPlayerActivity"
|
android:name=".player.ExoPlayerActivity"
|
||||||
android:configChanges="keyboard|keyboardHidden|orientation|screenSize"
|
android:configChanges="keyboard|keyboardHidden|orientation|screenSize"
|
||||||
|
@ -103,12 +105,14 @@
|
||||||
<data android:scheme="file" />
|
<data android:scheme="file" />
|
||||||
</intent-filter>
|
</intent-filter>
|
||||||
</activity>
|
</activity>
|
||||||
|
|
||||||
<service
|
<service
|
||||||
android:name=".player.BackgroundPlayer"
|
android:name=".player.BackgroundPlayer"
|
||||||
android:label="@string/background_player_name"
|
android:exported="false"
|
||||||
android:exported="false" />
|
android:label="@string/background_player_name" />
|
||||||
|
|
||||||
<activity
|
<activity
|
||||||
android:name=".SettingsActivity"
|
android:name=".settings.SettingsActivity"
|
||||||
android:label="@string/settings_activity_title" />
|
android:label="@string/settings_activity_title" />
|
||||||
<activity
|
<activity
|
||||||
android:name=".PanicResponderActivity"
|
android:name=".PanicResponderActivity"
|
||||||
|
@ -131,12 +135,10 @@
|
||||||
<activity
|
<activity
|
||||||
android:name=".download.MainActivity"
|
android:name=".download.MainActivity"
|
||||||
android:label="@string/app_name"
|
android:label="@string/app_name"
|
||||||
android:theme="@style/AppTheme"
|
android:launchMode="singleTask"
|
||||||
android:launchMode="singleTask">
|
android:theme="@style/AppTheme" />
|
||||||
</activity>
|
|
||||||
|
|
||||||
<service
|
<service android:name="us.shandian.giga.service.DownloadManagerService" />
|
||||||
android:name="us.shandian.giga.service.DownloadManagerService"/>
|
|
||||||
|
|
||||||
<activity
|
<activity
|
||||||
android:name="com.nononsenseapps.filepicker.FilePickerActivity"
|
android:name="com.nononsenseapps.filepicker.FilePickerActivity"
|
||||||
|
@ -145,5 +147,11 @@
|
||||||
android:launchMode="singleTop">
|
android:launchMode="singleTop">
|
||||||
</activity>
|
</activity>
|
||||||
|
|
||||||
|
<activity
|
||||||
|
android:name=".ChannelActivity"
|
||||||
|
android:label="@string/title_activity_channel"
|
||||||
|
android:theme="@style/AppTheme.NoActionBar" />
|
||||||
|
|
||||||
</application>
|
</application>
|
||||||
|
|
||||||
</manifest>
|
</manifest>
|
|
@ -43,7 +43,7 @@ public class ActivityCommunicator {
|
||||||
public volatile Bitmap backgroundPlayerThumbnail;
|
public volatile Bitmap backgroundPlayerThumbnail;
|
||||||
|
|
||||||
// Sent from any activity to ErrorActivity.
|
// Sent from any activity to ErrorActivity.
|
||||||
public volatile List<Exception> errorList;
|
public volatile List<Throwable> errorList;
|
||||||
public volatile Class returnActivity;
|
public volatile Class returnActivity;
|
||||||
public volatile ErrorActivity.ErrorInfo errorInfo;
|
public volatile ErrorActivity.ErrorInfo errorInfo;
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,12 +2,12 @@ package org.schabi.newpipe;
|
||||||
|
|
||||||
import android.app.Application;
|
import android.app.Application;
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
import android.content.SharedPreferences;
|
|
||||||
import android.preference.PreferenceManager;
|
|
||||||
|
|
||||||
import com.nostra13.universalimageloader.core.ImageLoader;
|
import com.nostra13.universalimageloader.core.ImageLoader;
|
||||||
import com.nostra13.universalimageloader.core.ImageLoaderConfiguration;
|
import com.nostra13.universalimageloader.core.ImageLoaderConfiguration;
|
||||||
|
|
||||||
|
import org.schabi.newpipe.settings.SettingsActivity;
|
||||||
|
|
||||||
import info.guardianproject.netcipher.NetCipher;
|
import info.guardianproject.netcipher.NetCipher;
|
||||||
import info.guardianproject.netcipher.proxy.OrbotHelper;
|
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.io.InputStreamReader;
|
||||||
import java.net.URL;
|
import java.net.URL;
|
||||||
import java.net.UnknownHostException;
|
import java.net.UnknownHostException;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.Iterator;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
import javax.net.ssl.HttpsURLConnection;
|
import javax.net.ssl.HttpsURLConnection;
|
||||||
|
|
||||||
import info.guardianproject.netcipher.NetCipher;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Created by Christian Schabesberger on 28.01.16.
|
* 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
|
* @param language the language (usually a 2-character code) to set as the preferred language
|
||||||
* @return the contents of the specified text file*/
|
* @return the contents of the specified text file*/
|
||||||
public String download(String siteUrl, String language) throws IOException {
|
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);
|
URL url = new URL(siteUrl);
|
||||||
HttpsURLConnection con = (HttpsURLConnection) url.openConnection();
|
HttpsURLConnection con = (HttpsURLConnection) url.openConnection();
|
||||||
//HttpsURLConnection con = NetCipher.getHttpsURLConnection(url);
|
Iterator it = customProperties.entrySet().iterator();
|
||||||
con.setRequestProperty("Accept-Language", language);
|
while(it.hasNext()) {
|
||||||
|
Map.Entry pair = (Map.Entry)it.next();
|
||||||
|
con.setRequestProperty((String)pair.getKey(), (String)pair.getValue());
|
||||||
|
}
|
||||||
return dl(con);
|
return dl(con);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -79,16 +79,19 @@ public class ErrorActivity extends AppCompatActivity {
|
||||||
public static final int GET_SUGGESTIONS = 2;
|
public static final int GET_SUGGESTIONS = 2;
|
||||||
public static final int SOMETHING_ELSE = 3;
|
public static final int SOMETHING_ELSE = 3;
|
||||||
public static final int USER_REPORT = 4;
|
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 SEARCHED_STRING = "searched";
|
||||||
public static final String REQUESTED_STREAM_STRING = "requested stream";
|
public static final String REQUESTED_STREAM_STRING = "requested stream";
|
||||||
public static final String GET_SUGGESTIONS_STRING = "get suggestions";
|
public static final String GET_SUGGESTIONS_STRING = "get suggestions";
|
||||||
public static final String SOMETHING_ELSE_STRING = "something";
|
public static final String SOMETHING_ELSE_STRING = "something";
|
||||||
public static final String USER_REPORT_STRING = "user report";
|
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_ADDRESS = "crashreport@newpipe.schabi.org";
|
||||||
public static final String ERROR_EMAIL_SUBJECT = "Exception in NewPipe " + BuildConfig.VERSION_NAME;
|
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 ErrorInfo errorInfo;
|
||||||
private Class returnActivity;
|
private Class returnActivity;
|
||||||
private String currentTimeStamp;
|
private String currentTimeStamp;
|
||||||
|
@ -102,7 +105,7 @@ public class ErrorActivity extends AppCompatActivity {
|
||||||
private TextView infoView;
|
private TextView infoView;
|
||||||
private TextView errorMessageView;
|
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) {
|
final Class returnAcitivty, View rootView, final ErrorInfo errorInfo) {
|
||||||
|
|
||||||
if (rootView != null) {
|
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) {
|
final Class returnAcitivty, View rootView, final ErrorInfo errorInfo) {
|
||||||
List<Exception> el = null;
|
List<Throwable> el = null;
|
||||||
if(e != null) {
|
if(e != null) {
|
||||||
el = new Vector<>();
|
el = new Vector<>();
|
||||||
el.add(e);
|
el.add(e);
|
||||||
|
@ -140,10 +143,10 @@ public class ErrorActivity extends AppCompatActivity {
|
||||||
}
|
}
|
||||||
|
|
||||||
// async call
|
// 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) {
|
final Class returnAcitivty, final View rootView, final ErrorInfo errorInfo) {
|
||||||
|
|
||||||
List<Exception> el = null;
|
List<Throwable> el = null;
|
||||||
if(e != null) {
|
if(e != null) {
|
||||||
el = new Vector<>();
|
el = new Vector<>();
|
||||||
el.add(e);
|
el.add(e);
|
||||||
|
@ -152,7 +155,7 @@ public class ErrorActivity extends AppCompatActivity {
|
||||||
}
|
}
|
||||||
|
|
||||||
// async call
|
// 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) {
|
final Class returnAcitivty, final View rootView, final ErrorInfo errorInfo) {
|
||||||
handler.post(new Runnable() {
|
handler.post(new Runnable() {
|
||||||
@Override
|
@Override
|
||||||
|
@ -171,7 +174,7 @@ public class ErrorActivity extends AppCompatActivity {
|
||||||
actionBar.setDisplayHomeAsUpEnabled(true);
|
actionBar.setDisplayHomeAsUpEnabled(true);
|
||||||
actionBar.setTitle(R.string.error_report_title);
|
actionBar.setTitle(R.string.error_report_title);
|
||||||
actionBar.setDisplayShowTitleEnabled(true);
|
actionBar.setDisplayShowTitleEnabled(true);
|
||||||
} catch (Exception e) {
|
} catch (Throwable e) {
|
||||||
Log.e(TAG, "Error turing exception handling");
|
Log.e(TAG, "Error turing exception handling");
|
||||||
e.printStackTrace();
|
e.printStackTrace();
|
||||||
}
|
}
|
||||||
|
@ -252,10 +255,10 @@ public class ErrorActivity extends AppCompatActivity {
|
||||||
return sw.getBuffer().toString();
|
return sw.getBuffer().toString();
|
||||||
}
|
}
|
||||||
|
|
||||||
private String formErrorText(List<Exception> el) {
|
private String formErrorText(List<Throwable> el) {
|
||||||
String text = "";
|
String text = "";
|
||||||
if(el != null) {
|
if(el != null) {
|
||||||
for (Exception e : el) {
|
for (Throwable e : el) {
|
||||||
text += "-------------------------------------\n"
|
text += "-------------------------------------\n"
|
||||||
+ getStackTrace(e);
|
+ getStackTrace(e);
|
||||||
}
|
}
|
||||||
|
@ -273,7 +276,7 @@ public class ErrorActivity extends AppCompatActivity {
|
||||||
returnActivity.isAssignableFrom(Activity.class)) {
|
returnActivity.isAssignableFrom(Activity.class)) {
|
||||||
intent = new Intent(this, returnActivity);
|
intent = new Intent(this, returnActivity);
|
||||||
} else {
|
} else {
|
||||||
intent = new Intent(this, VideoItemListActivity.class);
|
intent = new Intent(this, MainActivity.class);
|
||||||
}
|
}
|
||||||
intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
|
intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
|
||||||
NavUtils.navigateUpTo(this, intent);
|
NavUtils.navigateUpTo(this, intent);
|
||||||
|
@ -313,7 +316,7 @@ public class ErrorActivity extends AppCompatActivity {
|
||||||
|
|
||||||
JSONArray exceptionArray = new JSONArray();
|
JSONArray exceptionArray = new JSONArray();
|
||||||
if(errorList != null) {
|
if(errorList != null) {
|
||||||
for (Exception e : errorList) {
|
for (Throwable e : errorList) {
|
||||||
exceptionArray.put(getStackTrace(e));
|
exceptionArray.put(getStackTrace(e));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -322,7 +325,7 @@ public class ErrorActivity extends AppCompatActivity {
|
||||||
errorObject.put("user_comment", userCommentBox.getText().toString());
|
errorObject.put("user_comment", userCommentBox.getText().toString());
|
||||||
|
|
||||||
return errorObject.toString(3);
|
return errorObject.toString(3);
|
||||||
} catch (Exception e) {
|
} catch (Throwable e) {
|
||||||
Log.e(TAG, "Error while erroring: Could not build json");
|
Log.e(TAG, "Error while erroring: Could not build json");
|
||||||
e.printStackTrace();
|
e.printStackTrace();
|
||||||
}
|
}
|
||||||
|
@ -342,6 +345,8 @@ public class ErrorActivity extends AppCompatActivity {
|
||||||
return SOMETHING_ELSE_STRING;
|
return SOMETHING_ELSE_STRING;
|
||||||
case USER_REPORT:
|
case USER_REPORT:
|
||||||
return USER_REPORT_STRING;
|
return USER_REPORT_STRING;
|
||||||
|
case LOAD_IMAGE:
|
||||||
|
return LOAD_IMAGE_STRING;
|
||||||
default:
|
default:
|
||||||
return "Your description is in another castle.";
|
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)
|
ipRange = Parser.matchGroup1("([0-9]*\\.[0-9]*\\.)[0-9]*\\.[0-9]*", ip)
|
||||||
+ "0.0";
|
+ "0.0";
|
||||||
} catch(Exception e) {
|
} catch(Throwable e) {
|
||||||
Log.d(TAG, "Error while error: could not get iprange");
|
Log.d(TAG, "Error while error: could not get iprange");
|
||||||
e.printStackTrace();
|
e.printStackTrace();
|
||||||
} finally {
|
} 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.Intent;
|
||||||
import android.content.SharedPreferences;
|
import android.content.SharedPreferences;
|
||||||
|
@ -11,8 +11,9 @@ import android.view.MenuInflater;
|
||||||
import android.view.MenuItem;
|
import android.view.MenuItem;
|
||||||
import android.widget.ArrayAdapter;
|
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.MediaFormat;
|
||||||
import org.schabi.newpipe.extractor.StreamInfo;
|
|
||||||
import org.schabi.newpipe.extractor.VideoStream;
|
import org.schabi.newpipe.extractor.VideoStream;
|
||||||
|
|
||||||
import java.util.List;
|
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.content.Intent;
|
||||||
import android.media.AudioManager;
|
import android.media.AudioManager;
|
||||||
|
@ -11,6 +11,9 @@ import android.view.Menu;
|
||||||
import android.view.MenuItem;
|
import android.view.MenuItem;
|
||||||
import android.widget.Toast;
|
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.ServiceList;
|
||||||
import org.schabi.newpipe.extractor.StreamingService;
|
import org.schabi.newpipe.extractor.StreamingService;
|
||||||
|
|
||||||
|
@ -85,7 +88,7 @@ public class VideoItemDetailActivity extends AppCompatActivity {
|
||||||
.show();
|
.show();
|
||||||
}
|
}
|
||||||
//arguments.putString(VideoItemDetailFragment.VIDEO_URL,
|
//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.putString(VideoItemDetailFragment.VIDEO_URL, videoUrl);
|
||||||
|
|
||||||
arguments.putBoolean(VideoItemDetailFragment.AUTO_PLAY,
|
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
|
// 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);
|
intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
|
||||||
NavUtils.navigateUpTo(this, intent);
|
NavUtils.navigateUpTo(this, intent);
|
||||||
return true;
|
return true;
|
|
@ -1,22 +1,21 @@
|
||||||
package org.schabi.newpipe;
|
package org.schabi.newpipe.detail;
|
||||||
|
|
||||||
import android.app.Activity;
|
import android.app.Activity;
|
||||||
import android.content.Context;
|
|
||||||
import android.content.DialogInterface;
|
import android.content.DialogInterface;
|
||||||
import android.content.Intent;
|
import android.content.Intent;
|
||||||
import android.content.res.TypedArray;
|
|
||||||
import android.graphics.Bitmap;
|
import android.graphics.Bitmap;
|
||||||
import android.graphics.BitmapFactory;
|
import android.graphics.BitmapFactory;
|
||||||
import android.graphics.Point;
|
import android.graphics.Point;
|
||||||
import android.net.Uri;
|
import android.net.Uri;
|
||||||
import android.os.Build;
|
import android.os.Build;
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
import android.os.Handler;
|
|
||||||
import android.preference.PreferenceManager;
|
import android.preference.PreferenceManager;
|
||||||
import android.support.design.widget.FloatingActionButton;
|
import android.support.design.widget.FloatingActionButton;
|
||||||
import android.support.v4.app.Fragment;
|
import android.support.v4.app.Fragment;
|
||||||
import android.support.v7.app.AlertDialog;
|
import android.support.v7.app.AlertDialog;
|
||||||
import android.support.v7.app.AppCompatActivity;
|
import android.support.v7.app.AppCompatActivity;
|
||||||
|
import android.support.v7.widget.LinearLayoutManager;
|
||||||
|
import android.support.v7.widget.RecyclerView;
|
||||||
import android.text.Html;
|
import android.text.Html;
|
||||||
import android.text.method.LinkMovementMethod;
|
import android.text.method.LinkMovementMethod;
|
||||||
import android.util.Log;
|
import android.util.Log;
|
||||||
|
@ -27,38 +26,34 @@ import android.view.MotionEvent;
|
||||||
import android.view.View;
|
import android.view.View;
|
||||||
import android.view.ViewGroup;
|
import android.view.ViewGroup;
|
||||||
import android.widget.Button;
|
import android.widget.Button;
|
||||||
import android.widget.FrameLayout;
|
|
||||||
import android.widget.ImageView;
|
import android.widget.ImageView;
|
||||||
import android.widget.LinearLayout;
|
|
||||||
import android.widget.ProgressBar;
|
import android.widget.ProgressBar;
|
||||||
import android.widget.RelativeLayout;
|
import android.widget.RelativeLayout;
|
||||||
import android.widget.TextView;
|
import android.widget.TextView;
|
||||||
import android.view.MenuItem;
|
import android.view.MenuItem;
|
||||||
import android.widget.Toast;
|
import android.widget.Toast;
|
||||||
|
|
||||||
import java.io.IOException;
|
|
||||||
|
|
||||||
import com.google.android.exoplayer.util.Util;
|
import com.google.android.exoplayer.util.Util;
|
||||||
import com.nostra13.universalimageloader.core.DisplayImageOptions;
|
import com.nostra13.universalimageloader.core.DisplayImageOptions;
|
||||||
import com.nostra13.universalimageloader.core.ImageLoader;
|
import com.nostra13.universalimageloader.core.ImageLoader;
|
||||||
import com.nostra13.universalimageloader.core.assist.FailReason;
|
import com.nostra13.universalimageloader.core.assist.FailReason;
|
||||||
import com.nostra13.universalimageloader.core.listener.ImageLoadingListener;
|
import com.nostra13.universalimageloader.core.listener.ImageLoadingListener;
|
||||||
|
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.Vector;
|
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.download.DownloadDialog;
|
||||||
import org.schabi.newpipe.extractor.AudioStream;
|
import org.schabi.newpipe.extractor.AudioStream;
|
||||||
import org.schabi.newpipe.extractor.MediaFormat;
|
import org.schabi.newpipe.extractor.MediaFormat;
|
||||||
import org.schabi.newpipe.extractor.ParsingException;
|
|
||||||
import org.schabi.newpipe.extractor.ServiceList;
|
import org.schabi.newpipe.extractor.ServiceList;
|
||||||
import org.schabi.newpipe.extractor.StreamExtractor;
|
|
||||||
import org.schabi.newpipe.extractor.StreamInfo;
|
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.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.BackgroundPlayer;
|
||||||
import org.schabi.newpipe.player.PlayVideoActivity;
|
import org.schabi.newpipe.player.PlayVideoActivity;
|
||||||
import org.schabi.newpipe.player.ExoPlayerActivity;
|
import org.schabi.newpipe.player.ExoPlayerActivity;
|
||||||
|
@ -102,19 +97,20 @@ public class VideoItemDetailFragment extends Fragment {
|
||||||
private int streamingServiceId = -1;
|
private int streamingServiceId = -1;
|
||||||
|
|
||||||
private boolean autoPlayEnabled;
|
private boolean autoPlayEnabled;
|
||||||
private boolean showNextVideoItem;
|
private boolean showNextStreamItem;
|
||||||
private Bitmap videoThumbnail;
|
|
||||||
|
|
||||||
private View thumbnailWindowLayout;
|
private View thumbnailWindowLayout;
|
||||||
//this only remains due to downwards compatibility
|
//this only remains due to downwards compatibility
|
||||||
private FloatingActionButton playVideoButton;
|
private FloatingActionButton playVideoButton;
|
||||||
private final Point initialThumbnailPos = new Point(0, 0);
|
private final Point initialThumbnailPos = new Point(0, 0);
|
||||||
|
private View rootView = null;
|
||||||
|
private Bitmap streamThumbnail = null;
|
||||||
|
|
||||||
private ImageLoader imageLoader = ImageLoader.getInstance();
|
private ImageLoader imageLoader = ImageLoader.getInstance();
|
||||||
private DisplayImageOptions displayImageOptions =
|
private DisplayImageOptions displayImageOptions =
|
||||||
new DisplayImageOptions.Builder().cacheInMemory(true).build();
|
new DisplayImageOptions.Builder().cacheInMemory(true).build();
|
||||||
|
|
||||||
|
private InfoListAdapter similarStreamsAdapter = null;
|
||||||
|
|
||||||
public interface OnInvokeCreateOptionsMenuListener {
|
public interface OnInvokeCreateOptionsMenuListener {
|
||||||
void createOptionsMenu();
|
void createOptionsMenu();
|
||||||
|
@ -122,215 +118,57 @@ public class VideoItemDetailFragment extends Fragment {
|
||||||
|
|
||||||
private OnInvokeCreateOptionsMenuListener onInvokeCreateOptionsMenuListener;
|
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) {
|
private void updateInfo(final StreamInfo info) {
|
||||||
try {
|
try {
|
||||||
Context c = getContext();
|
Activity a = getActivity();
|
||||||
VideoInfoItemViewCreator videoItemViewCreator =
|
|
||||||
new VideoInfoItemViewCreator(LayoutInflater.from(getActivity()));
|
|
||||||
|
|
||||||
RelativeLayout textContentLayout =
|
RelativeLayout textContentLayout =
|
||||||
(RelativeLayout) activity.findViewById(R.id.detailTextContentLayout);
|
(RelativeLayout) activity.findViewById(R.id.detail_text_content_layout);
|
||||||
final TextView videoTitleView =
|
final TextView videoTitleView =
|
||||||
(TextView) activity.findViewById(R.id.detailVideoTitleView);
|
(TextView) activity.findViewById(R.id.detail_video_title_view);
|
||||||
TextView uploaderView = (TextView) activity.findViewById(R.id.detailUploaderView);
|
TextView uploaderView = (TextView) activity.findViewById(R.id.detail_uploader_view);
|
||||||
TextView viewCountView = (TextView) activity.findViewById(R.id.detailViewCountView);
|
TextView viewCountView = (TextView) activity.findViewById(R.id.detail_view_count_view);
|
||||||
TextView thumbsUpView = (TextView) activity.findViewById(R.id.detailThumbsUpCountView);
|
TextView thumbsUpView = (TextView) activity.findViewById(R.id.detail_thumbs_up_count_view);
|
||||||
TextView thumbsDownView =
|
TextView thumbsDownView =
|
||||||
(TextView) activity.findViewById(R.id.detailThumbsDownCountView);
|
(TextView) activity.findViewById(R.id.detail_thumbs_down_count_view);
|
||||||
TextView uploadDateView = (TextView) activity.findViewById(R.id.detailUploadDateView);
|
TextView uploadDateView = (TextView) activity.findViewById(R.id.detail_upload_date_view);
|
||||||
TextView descriptionView = (TextView) activity.findViewById(R.id.detailDescriptionView);
|
TextView descriptionView = (TextView) activity.findViewById(R.id.detail_description_view);
|
||||||
FrameLayout nextVideoFrame =
|
RecyclerView nextStreamView =
|
||||||
(FrameLayout) activity.findViewById(R.id.detailNextVideoFrame);
|
(RecyclerView) activity.findViewById(R.id.detail_next_stream_content);
|
||||||
RelativeLayout nextVideoRootFrame =
|
RelativeLayout nextVideoRootFrame =
|
||||||
(RelativeLayout) activity.findViewById(R.id.detailNextVideoRootLayout);
|
(RelativeLayout) activity.findViewById(R.id.detail_next_stream_root_layout);
|
||||||
Button nextVideoButton = (Button) activity.findViewById(R.id.detailNextVideoButton);
|
TextView similarTitle = (TextView) activity.findViewById(R.id.detail_similar_title);
|
||||||
TextView similarTitle = (TextView) activity.findViewById(R.id.detailSimilarTitle);
|
|
||||||
Button backgroundButton = (Button)
|
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 topView = activity.findViewById(R.id.detailTopView);
|
||||||
View nextVideoView = null;
|
Button channelButton = (Button) activity.findViewById(R.id.channel_button);
|
||||||
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);
|
|
||||||
}
|
|
||||||
|
|
||||||
progressBar.setVisibility(View.GONE);
|
progressBar.setVisibility(View.GONE);
|
||||||
if(nextVideoView != null) {
|
if(info.next_video != null) {
|
||||||
nextVideoFrame.addView(nextVideoView);
|
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);
|
textContentLayout.setVisibility(View.VISIBLE);
|
||||||
if (android.os.Build.VERSION.SDK_INT < 18) {
|
if (android.os.Build.VERSION.SDK_INT < 18) {
|
||||||
playVideoButton.setVisibility(View.VISIBLE);
|
playVideoButton.setVisibility(View.VISIBLE);
|
||||||
} else {
|
} else {
|
||||||
ImageView playArrowView = (ImageView) activity.findViewById(R.id.playArrowView);
|
ImageView playArrowView = (ImageView) activity.findViewById(R.id.play_arrow_view);
|
||||||
playArrowView.setVisibility(View.VISIBLE);
|
playArrowView.setVisibility(View.VISIBLE);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!showNextVideoItem) {
|
if (!showNextStreamItem) {
|
||||||
nextVideoRootFrame.setVisibility(View.GONE);
|
nextVideoRootFrame.setVisibility(View.GONE);
|
||||||
similarTitle.setVisibility(View.GONE);
|
similarTitle.setVisibility(View.GONE);
|
||||||
}
|
}
|
||||||
|
@ -341,7 +179,7 @@ public class VideoItemDetailFragment extends Fragment {
|
||||||
@Override
|
@Override
|
||||||
public boolean onTouch(View v, MotionEvent event) {
|
public boolean onTouch(View v, MotionEvent event) {
|
||||||
if (event.getAction() == android.view.MotionEvent.ACTION_UP) {
|
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);
|
View extra = activity.findViewById(R.id.detailExtraView);
|
||||||
if (extra.getVisibility() == View.VISIBLE) {
|
if (extra.getVisibility() == View.VISIBLE) {
|
||||||
extra.setVisibility(View.GONE);
|
extra.setVisibility(View.GONE);
|
||||||
|
@ -361,29 +199,29 @@ public class VideoItemDetailFragment extends Fragment {
|
||||||
if(!info.uploader.isEmpty()) {
|
if(!info.uploader.isEmpty()) {
|
||||||
uploaderView.setText(info.uploader);
|
uploaderView.setText(info.uploader);
|
||||||
} else {
|
} else {
|
||||||
activity.findViewById(R.id.detailUploaderWrapView).setVisibility(View.GONE);
|
activity.findViewById(R.id.detail_uploader_view).setVisibility(View.GONE);
|
||||||
}
|
}
|
||||||
if(info.view_count >= 0) {
|
if(info.view_count >= 0) {
|
||||||
viewCountView.setText(Localization.localizeViewCount(info.view_count, c));
|
viewCountView.setText(Localization.localizeViewCount(info.view_count, a));
|
||||||
} else {
|
} else {
|
||||||
viewCountView.setVisibility(View.GONE);
|
viewCountView.setVisibility(View.GONE);
|
||||||
}
|
}
|
||||||
if(info.dislike_count >= 0) {
|
if(info.dislike_count >= 0) {
|
||||||
thumbsDownView.setText(Localization.localizeNumber(info.dislike_count, c));
|
thumbsDownView.setText(Localization.localizeNumber(info.dislike_count, a));
|
||||||
} else {
|
} else {
|
||||||
thumbsDownView.setVisibility(View.INVISIBLE);
|
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) {
|
if(info.like_count >= 0) {
|
||||||
thumbsUpView.setText(Localization.localizeNumber(info.like_count, c));
|
thumbsUpView.setText(Localization.localizeNumber(info.like_count, a));
|
||||||
} else {
|
} else {
|
||||||
thumbsUpView.setVisibility(View.GONE);
|
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);
|
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()) {
|
if(!info.upload_date.isEmpty()) {
|
||||||
uploadDateView.setText(Localization.localizeDate(info.upload_date, c));
|
uploadDateView.setText(Localization.localizeDate(info.upload_date, a));
|
||||||
} else {
|
} else {
|
||||||
uploadDateView.setVisibility(View.GONE);
|
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);
|
textContentLayout.setVisibility(View.VISIBLE);
|
||||||
|
|
||||||
if(info.related_videos != null && !info.related_videos.isEmpty()) {
|
if(info.next_video == null) {
|
||||||
initSimilarVideos(info, videoItemViewCreator);
|
activity.findViewById(R.id.detail_next_stream_title).setVisibility(View.GONE);
|
||||||
|
}
|
||||||
|
|
||||||
|
if(info.related_streams != null && !info.related_streams.isEmpty()) {
|
||||||
|
initSimilarVideos(info);
|
||||||
} else {
|
} else {
|
||||||
activity.findViewById(R.id.detailSimilarTitle).setVisibility(View.GONE);
|
activity.findViewById(R.id.detail_similar_title).setVisibility(View.GONE);
|
||||||
activity.findViewById(R.id.similarVideosView).setVisibility(View.GONE);
|
activity.findViewById(R.id.similar_streams_view).setVisibility(View.GONE);
|
||||||
}
|
}
|
||||||
|
|
||||||
setupActionBarHandler(info);
|
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) {
|
} catch (java.lang.NullPointerException e) {
|
||||||
Log.w(TAG, "updateInfo(): Fragment closed before thread ended work... or else");
|
Log.w(TAG, "updateInfo(): Fragment closed before thread ended work... or else");
|
||||||
e.printStackTrace();
|
e.printStackTrace();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void initThumbnailViews(StreamInfo info, View nextVideoFrame) {
|
private void initThumbnailViews(final StreamInfo info) {
|
||||||
ImageView videoThumbnailView = (ImageView) activity.findViewById(R.id.detailThumbnailView);
|
ImageView videoThumbnailView = (ImageView) activity.findViewById(R.id.detail_thumbnail_view);
|
||||||
ImageView uploaderThumb
|
ImageView uploaderThumb
|
||||||
= (ImageView) activity.findViewById(R.id.detailUploaderThumbnailView);
|
= (ImageView) activity.findViewById(R.id.detail_uploader_thumbnail_view);
|
||||||
ImageView nextVideoThumb =
|
|
||||||
(ImageView) nextVideoFrame.findViewById(R.id.itemThumbnailView);
|
|
||||||
|
|
||||||
if(info.thumbnail_url != null && !info.thumbnail_url.isEmpty()) {
|
if(info.thumbnail_url != null && !info.thumbnail_url.isEmpty()) {
|
||||||
imageLoader.displayImage(info.thumbnail_url, videoThumbnailView,
|
imageLoader.displayImage(info.thumbnail_url, videoThumbnailView,
|
||||||
|
@ -469,14 +312,16 @@ public class VideoItemDetailFragment extends Fragment {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onLoadingFailed(String imageUri, View view, FailReason failReason) {
|
public void onLoadingFailed(String imageUri, View view, FailReason failReason) {
|
||||||
Toast.makeText(VideoItemDetailFragment.this.getActivity(),
|
ErrorActivity.reportError(getActivity(),
|
||||||
R.string.could_not_load_thumbnails, Toast.LENGTH_LONG).show();
|
failReason.getCause(), null, rootView,
|
||||||
failReason.getCause().printStackTrace();
|
ErrorActivity.ErrorInfo.make(ErrorActivity.LOAD_IMAGE,
|
||||||
|
ServiceList.getNameOfService(info.service_id), imageUri,
|
||||||
|
R.string.could_not_load_thumbnails));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onLoadingComplete(String imageUri, View view, Bitmap loadedImage) {
|
public void onLoadingComplete(String imageUri, View view, Bitmap loadedImage) {
|
||||||
videoThumbnail = loadedImage;
|
streamThumbnail = loadedImage;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -488,11 +333,8 @@ public class VideoItemDetailFragment extends Fragment {
|
||||||
}
|
}
|
||||||
if(info.uploader_thumbnail_url != null && !info.uploader_thumbnail_url.isEmpty()) {
|
if(info.uploader_thumbnail_url != null && !info.uploader_thumbnail_url.isEmpty()) {
|
||||||
imageLoader.displayImage(info.uploader_thumbnail_url,
|
imageLoader.displayImage(info.uploader_thumbnail_url,
|
||||||
uploaderThumb, displayImageOptions, new ThumbnailLoadingListener());
|
uploaderThumb, displayImageOptions,
|
||||||
}
|
new ImageErrorLoadingListener(activity, rootView, info.service_id));
|
||||||
if(info.thumbnail_url != null && !info.thumbnail_url.isEmpty() && info.next_video != null) {
|
|
||||||
imageLoader.displayImage(info.next_video.thumbnail_url,
|
|
||||||
nextVideoThumb, displayImageOptions, new ThumbnailLoadingListener());
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -602,9 +444,9 @@ public class VideoItemDetailFragment extends Fragment {
|
||||||
info.audio_streams.get(getPreferredAudioStreamId(info));
|
info.audio_streams.get(getPreferredAudioStreamId(info));
|
||||||
if (!useExternalAudioPlayer && android.os.Build.VERSION.SDK_INT >= 18) {
|
if (!useExternalAudioPlayer && android.os.Build.VERSION.SDK_INT >= 18) {
|
||||||
//internal music player: explicit intent
|
//internal music player: explicit intent
|
||||||
if (!BackgroundPlayer.isRunning && videoThumbnail != null) {
|
if (!BackgroundPlayer.isRunning && streamThumbnail != null) {
|
||||||
ActivityCommunicator.getCommunicator()
|
ActivityCommunicator.getCommunicator()
|
||||||
.backgroundPlayerThumbnail = videoThumbnail;
|
.backgroundPlayerThumbnail = streamThumbnail;
|
||||||
intent = new Intent(activity, BackgroundPlayer.class);
|
intent = new Intent(activity, BackgroundPlayer.class);
|
||||||
|
|
||||||
intent.setAction(Intent.ACTION_VIEW);
|
intent.setAction(Intent.ACTION_VIEW);
|
||||||
|
@ -684,47 +526,14 @@ public class VideoItemDetailFragment extends Fragment {
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void initSimilarVideos(final StreamInfo info, VideoInfoItemViewCreator videoItemViewCreator) {
|
private void initSimilarVideos(final StreamInfo info) {
|
||||||
LinearLayout similarLayout = (LinearLayout) activity.findViewById(R.id.similarVideosView);
|
similarStreamsAdapter.addStreamItemList(info.related_streams);
|
||||||
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 onErrorBlockedByGema() {
|
private void onErrorBlockedByGema() {
|
||||||
Button backgroundButton = (Button)
|
Button backgroundButton = (Button)
|
||||||
activity.findViewById(R.id.detailVideoThumbnailWindowBackgroundButton);
|
activity.findViewById(R.id.detail_stream_thumbnail_window_background_button);
|
||||||
ImageView thumbnailView = (ImageView) activity.findViewById(R.id.detailThumbnailView);
|
ImageView thumbnailView = (ImageView) activity.findViewById(R.id.detail_thumbnail_view);
|
||||||
|
|
||||||
progressBar.setVisibility(View.GONE);
|
progressBar.setVisibility(View.GONE);
|
||||||
thumbnailView.setImageBitmap(BitmapFactory.decodeResource(
|
thumbnailView.setImageBitmap(BitmapFactory.decodeResource(
|
||||||
|
@ -744,7 +553,7 @@ public class VideoItemDetailFragment extends Fragment {
|
||||||
}
|
}
|
||||||
|
|
||||||
private void onNotSpecifiedContentError() {
|
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);
|
progressBar.setVisibility(View.GONE);
|
||||||
thumbnailView.setImageBitmap(BitmapFactory.decodeResource(
|
thumbnailView.setImageBitmap(BitmapFactory.decodeResource(
|
||||||
getResources(), R.drawable.not_available_monkey));
|
getResources(), R.drawable.not_available_monkey));
|
||||||
|
@ -753,7 +562,7 @@ public class VideoItemDetailFragment extends Fragment {
|
||||||
}
|
}
|
||||||
|
|
||||||
private void onNotSpecifiedContentErrorWithMessage(int resourceId) {
|
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);
|
progressBar.setVisibility(View.GONE);
|
||||||
thumbnailView.setImageBitmap(BitmapFactory.decodeResource(
|
thumbnailView.setImageBitmap(BitmapFactory.decodeResource(
|
||||||
getResources(), R.drawable.not_available_monkey));
|
getResources(), R.drawable.not_available_monkey));
|
||||||
|
@ -779,16 +588,44 @@ public class VideoItemDetailFragment extends Fragment {
|
||||||
public void onCreate(Bundle savedInstanceState) {
|
public void onCreate(Bundle savedInstanceState) {
|
||||||
super.onCreate(savedInstanceState);
|
super.onCreate(savedInstanceState);
|
||||||
activity = (AppCompatActivity) getActivity();
|
activity = (AppCompatActivity) getActivity();
|
||||||
showNextVideoItem = PreferenceManager.getDefaultSharedPreferences(getActivity())
|
showNextStreamItem = PreferenceManager.getDefaultSharedPreferences(getActivity())
|
||||||
.getBoolean(activity.getString(R.string.show_next_video_key), true);
|
.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
|
@Override
|
||||||
public View onCreateView(LayoutInflater inflater, ViewGroup container,
|
public View onCreateView(LayoutInflater inflater, ViewGroup container,
|
||||||
Bundle savedInstanceState) {
|
Bundle savedInstanceState) {
|
||||||
View rootView = inflater.inflate(R.layout.fragment_videoitem_detail, container, false);
|
rootView = inflater.inflate(R.layout.fragment_videoitem_detail, container, false);
|
||||||
progressBar = (ProgressBar) rootView.findViewById(R.id.detailProgressBar);
|
progressBar = (ProgressBar) rootView.findViewById(R.id.detail_progress_bar);
|
||||||
|
|
||||||
actionBarHandler = new ActionBarHandler(activity);
|
actionBarHandler = new ActionBarHandler(activity);
|
||||||
actionBarHandler.setupNavMenu(activity);
|
actionBarHandler.setupNavMenu(activity);
|
||||||
|
@ -804,30 +641,25 @@ public class VideoItemDetailFragment extends Fragment {
|
||||||
super.onActivityCreated(savedInstanceBundle);
|
super.onActivityCreated(savedInstanceBundle);
|
||||||
Activity a = getActivity();
|
Activity a = getActivity();
|
||||||
if (android.os.Build.VERSION.SDK_INT < 18) {
|
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)
|
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
|
// Sometimes when this fragment is not visible it still gets initiated
|
||||||
// then we must not try to access objects of this fragment.
|
// then we must not try to access objects of this fragment.
|
||||||
// Otherwise the applications would crash.
|
// Otherwise the applications would crash.
|
||||||
if(backgroundButton != null) {
|
if(backgroundButton != null) {
|
||||||
try {
|
|
||||||
streamingServiceId = getArguments().getInt(STREAMING_SERVICE);
|
streamingServiceId = getArguments().getInt(STREAMING_SERVICE);
|
||||||
StreamingService streamingService = ServiceList.getService(streamingServiceId);
|
String videoUrl = getArguments().getString(VIDEO_URL);
|
||||||
Thread videoExtractorThread = new Thread(new VideoExtractorRunnable(
|
StreamInfoWorker siw = StreamInfoWorker.getInstance();
|
||||||
getArguments().getString(VIDEO_URL), streamingService));
|
siw.search(streamingServiceId, videoUrl, getActivity());
|
||||||
|
|
||||||
autoPlayEnabled = getArguments().getBoolean(AUTO_PLAY);
|
autoPlayEnabled = getArguments().getBoolean(AUTO_PLAY);
|
||||||
videoExtractorThread.start();
|
|
||||||
} catch (Exception e) {
|
|
||||||
e.printStackTrace();
|
|
||||||
}
|
|
||||||
|
|
||||||
if(Build.VERSION.SDK_INT >= 18) {
|
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() {
|
thumbnailView.addOnLayoutChangeListener(new View.OnLayoutChangeListener() {
|
||||||
// This is used to synchronize the thumbnailWindowButton and the playVideoButton
|
// This is used to synchronize the thumbnailWindowButton and the playVideoButton
|
||||||
// inside the ScrollView with the actual size of the thumbnail.
|
// 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;
|
this.onInvokeCreateOptionsMenuListener = listener;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void postNewErrorToast(Handler h, final int stringResource) {
|
private void postNewErrorToast(final int stringResource) {
|
||||||
h.post(new Runnable() {
|
|
||||||
@Override
|
|
||||||
public void run() {
|
|
||||||
Toast.makeText(VideoItemDetailFragment.this.getActivity(),
|
Toast.makeText(VideoItemDetailFragment.this.getActivity(),
|
||||||
stringResource, Toast.LENGTH_LONG).show();
|
stringResource, Toast.LENGTH_LONG).show();
|
||||||
}
|
}
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
private void postNewErrorToast(Handler h, final String message) {
|
private void openStreamUrl(String url) {
|
||||||
h.post(new Runnable() {
|
Intent detailIntent = new Intent(activity, VideoItemDetailActivity.class);
|
||||||
@Override
|
detailIntent.putExtra(VideoItemDetailFragment.VIDEO_URL, url);
|
||||||
public void run() {
|
detailIntent.putExtra(
|
||||||
Toast.makeText(VideoItemDetailFragment.this.getActivity(),
|
VideoItemDetailFragment.STREAMING_SERVICE, streamingServiceId);
|
||||||
message, Toast.LENGTH_LONG).show();
|
activity.startActivity(detailIntent);
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -25,7 +25,6 @@ import android.widget.SeekBar;
|
||||||
import android.widget.TextView;
|
import android.widget.TextView;
|
||||||
|
|
||||||
import org.schabi.newpipe.App;
|
import org.schabi.newpipe.App;
|
||||||
import org.schabi.newpipe.NewPipeSettings;
|
|
||||||
import org.schabi.newpipe.R;
|
import org.schabi.newpipe.R;
|
||||||
|
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
|
|
|
@ -8,14 +8,12 @@ import android.content.Context;
|
||||||
import android.content.Intent;
|
import android.content.Intent;
|
||||||
import android.content.ServiceConnection;
|
import android.content.ServiceConnection;
|
||||||
import android.content.SharedPreferences;
|
import android.content.SharedPreferences;
|
||||||
import android.os.AsyncTask;
|
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
import android.os.IBinder;
|
import android.os.IBinder;
|
||||||
import android.support.v4.app.NavUtils;
|
import android.support.v4.app.NavUtils;
|
||||||
import android.support.v7.app.ActionBar;
|
import android.support.v7.app.ActionBar;
|
||||||
import android.support.v7.app.AppCompatActivity;
|
import android.support.v7.app.AppCompatActivity;
|
||||||
import android.support.v7.widget.Toolbar;
|
import android.support.v7.widget.Toolbar;
|
||||||
import android.util.Log;
|
|
||||||
import android.view.LayoutInflater;
|
import android.view.LayoutInflater;
|
||||||
import android.view.Menu;
|
import android.view.Menu;
|
||||||
import android.view.MenuInflater;
|
import android.view.MenuInflater;
|
||||||
|
@ -23,25 +21,16 @@ import android.view.MenuItem;
|
||||||
import android.view.View;
|
import android.view.View;
|
||||||
import android.view.ViewTreeObserver;
|
import android.view.ViewTreeObserver;
|
||||||
import android.widget.AdapterView;
|
import android.widget.AdapterView;
|
||||||
import android.widget.Button;
|
|
||||||
import android.widget.EditText;
|
import android.widget.EditText;
|
||||||
import android.widget.SeekBar;
|
import android.widget.SeekBar;
|
||||||
import android.widget.TextView;
|
import android.widget.TextView;
|
||||||
import android.widget.Toast;
|
import android.widget.Toast;
|
||||||
import android.support.v7.widget.SearchView;
|
|
||||||
|
|
||||||
import org.schabi.newpipe.ErrorActivity;
|
import org.schabi.newpipe.ErrorActivity;
|
||||||
import org.schabi.newpipe.R;
|
import org.schabi.newpipe.R;
|
||||||
import org.schabi.newpipe.SettingsActivity;
|
import org.schabi.newpipe.settings.SettingsActivity;
|
||||||
import org.schabi.newpipe.VideoItemDetailActivity;
|
|
||||||
import org.schabi.newpipe.VideoItemListActivity;
|
|
||||||
import org.schabi.newpipe.extractor.ServiceList;
|
|
||||||
|
|
||||||
import java.io.File;
|
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 java.util.Vector;
|
||||||
|
|
||||||
import us.shandian.giga.get.DownloadManager;
|
import us.shandian.giga.get.DownloadManager;
|
||||||
|
@ -257,7 +246,7 @@ public class MainActivity extends AppCompatActivity implements AdapterView.OnIte
|
||||||
|
|
||||||
switch (id) {
|
switch (id) {
|
||||||
case android.R.id.home: {
|
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);
|
intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
|
||||||
NavUtils.navigateUpTo(this, intent);
|
NavUtils.navigateUpTo(this, intent);
|
||||||
return true;
|
return true;
|
||||||
|
@ -268,7 +257,7 @@ public class MainActivity extends AppCompatActivity implements AdapterView.OnIte
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
case R.id.action_report_error: {
|
case R.id.action_report_error: {
|
||||||
ErrorActivity.reportError(MainActivity.this, new Vector<Exception>(),
|
ErrorActivity.reportError(MainActivity.this, new Vector<Throwable>(),
|
||||||
null, null,
|
null, null,
|
||||||
ErrorActivity.ErrorInfo.make(ErrorActivity.USER_REPORT,
|
ErrorActivity.ErrorInfo.make(ErrorActivity.USER_REPORT,
|
||||||
null,
|
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;
|
package org.schabi.newpipe.extractor;
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Created by Christian Schabesberger on 28.01.16.
|
* Created by Christian Schabesberger on 28.01.16.
|
||||||
|
@ -32,6 +33,14 @@ public interface Downloader {
|
||||||
* @throws IOException*/
|
* @throws IOException*/
|
||||||
String download(String siteUrl, String language) 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.
|
/**Download (via HTTP) the text file located at the supplied URL, and return its contents.
|
||||||
* Primarily intended for downloading web pages.
|
* Primarily intended for downloading web pages.
|
||||||
* @param siteUrl the URL of the text file to download
|
* @param siteUrl the URL of the text file to download
|
||||||
|
|
|
@ -33,7 +33,7 @@ public abstract class SearchEngine {
|
||||||
|
|
||||||
private StreamPreviewInfoSearchCollector collector;
|
private StreamPreviewInfoSearchCollector collector;
|
||||||
|
|
||||||
public SearchEngine(StreamUrlIdHandler urlIdHandler, int serviceId) {
|
public SearchEngine(UrlIdHandler urlIdHandler, int serviceId) {
|
||||||
collector = new StreamPreviewInfoSearchCollector(urlIdHandler, serviceId);
|
collector = new StreamPreviewInfoSearchCollector(urlIdHandler, serviceId);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -43,5 +43,5 @@ public class SearchResult {
|
||||||
|
|
||||||
public String suggestion = "";
|
public String suggestion = "";
|
||||||
public List<StreamPreviewInfo> resultList = new Vector<>();
|
public List<StreamPreviewInfo> resultList = new Vector<>();
|
||||||
public List<Exception> errors = new Vector<>();
|
public List<Throwable> errors = new Vector<>();
|
||||||
}
|
}
|
||||||
|
|
|
@ -30,7 +30,7 @@ public abstract class StreamExtractor {
|
||||||
|
|
||||||
private int serviceId;
|
private int serviceId;
|
||||||
private String url;
|
private String url;
|
||||||
private StreamUrlIdHandler urlIdHandler;
|
private UrlIdHandler urlIdHandler;
|
||||||
private Downloader downloader;
|
private Downloader downloader;
|
||||||
private StreamPreviewInfoCollector previewInfoCollector;
|
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.serviceId = serviceId;
|
||||||
this.urlIdHandler = urlIdHandler;
|
this.urlIdHandler = urlIdHandler;
|
||||||
previewInfoCollector = new StreamPreviewInfoCollector(urlIdHandler, serviceId);
|
previewInfoCollector = new StreamPreviewInfoCollector(urlIdHandler, serviceId);
|
||||||
|
@ -69,7 +69,7 @@ public abstract class StreamExtractor {
|
||||||
return url;
|
return url;
|
||||||
}
|
}
|
||||||
|
|
||||||
public StreamUrlIdHandler getUrlIdHandler() {
|
public UrlIdHandler getUrlIdHandler() {
|
||||||
return urlIdHandler;
|
return urlIdHandler;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -81,6 +81,7 @@ public abstract class StreamExtractor {
|
||||||
public abstract String getTitle() throws ParsingException;
|
public abstract String getTitle() throws ParsingException;
|
||||||
public abstract String getDescription() throws ParsingException;
|
public abstract String getDescription() throws ParsingException;
|
||||||
public abstract String getUploader() throws ParsingException;
|
public abstract String getUploader() throws ParsingException;
|
||||||
|
public abstract String getChannelUrl() throws ParsingException;
|
||||||
public abstract int getLength() throws ParsingException;
|
public abstract int getLength() throws ParsingException;
|
||||||
public abstract long getViewCount() throws ParsingException;
|
public abstract long getViewCount() throws ParsingException;
|
||||||
public abstract String getUploadDate() 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: ---- */
|
/* ---- 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.
|
// 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.service_id = extractor.getServiceId();
|
||||||
streamInfo.webpage_url = extractor.getPageUrl();
|
streamInfo.webpage_url = extractor.getPageUrl();
|
||||||
streamInfo.stream_type = extractor.getStreamType();
|
streamInfo.stream_type = extractor.getStreamType();
|
||||||
streamInfo.id = uiconv.getVideoId(extractor.getPageUrl());
|
streamInfo.id = uiconv.getId(extractor.getPageUrl());
|
||||||
streamInfo.title = extractor.getTitle();
|
streamInfo.title = extractor.getTitle();
|
||||||
streamInfo.age_limit = extractor.getAgeLimit();
|
streamInfo.age_limit = extractor.getAgeLimit();
|
||||||
|
|
||||||
|
@ -188,6 +188,11 @@ public class StreamInfo extends AbstractVideoInfo {
|
||||||
} catch(Exception e) {
|
} catch(Exception e) {
|
||||||
streamInfo.addException(e);
|
streamInfo.addException(e);
|
||||||
}
|
}
|
||||||
|
try {
|
||||||
|
streamInfo.channel_url = extractor.getChannelUrl();
|
||||||
|
} catch(Exception e) {
|
||||||
|
streamInfo.addException(e);
|
||||||
|
}
|
||||||
try {
|
try {
|
||||||
streamInfo.description = extractor.getDescription();
|
streamInfo.description = extractor.getDescription();
|
||||||
} catch(Exception e) {
|
} catch(Exception e) {
|
||||||
|
@ -248,7 +253,7 @@ public class StreamInfo extends AbstractVideoInfo {
|
||||||
try {
|
try {
|
||||||
// get related videos
|
// get related videos
|
||||||
StreamPreviewInfoCollector c = extractor.getRelatedVideos();
|
StreamPreviewInfoCollector c = extractor.getRelatedVideos();
|
||||||
streamInfo.related_videos = c.getItemList();
|
streamInfo.related_streams = c.getItemList();
|
||||||
streamInfo.errors.addAll(c.getErrors());
|
streamInfo.errors.addAll(c.getErrors());
|
||||||
} catch(Exception e) {
|
} catch(Exception e) {
|
||||||
streamInfo.addException(e);
|
streamInfo.addException(e);
|
||||||
|
@ -258,6 +263,7 @@ public class StreamInfo extends AbstractVideoInfo {
|
||||||
}
|
}
|
||||||
|
|
||||||
public String uploader_thumbnail_url = "";
|
public String uploader_thumbnail_url = "";
|
||||||
|
public String channel_url = "";
|
||||||
public String description = "";
|
public String description = "";
|
||||||
|
|
||||||
public List<VideoStream> video_streams = null;
|
public List<VideoStream> video_streams = null;
|
||||||
|
@ -275,9 +281,9 @@ public class StreamInfo extends AbstractVideoInfo {
|
||||||
public int dislike_count = -1;
|
public int dislike_count = -1;
|
||||||
public String average_rating = "";
|
public String average_rating = "";
|
||||||
public StreamPreviewInfo next_video = null;
|
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!
|
//in seconds. some metadata is not passed using a StreamInfo object!
|
||||||
public int start_position = 0;
|
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 {
|
public class StreamPreviewInfoCollector {
|
||||||
private List<StreamPreviewInfo> itemList = new Vector<>();
|
private List<StreamPreviewInfo> itemList = new Vector<>();
|
||||||
private List<Exception> errors = new Vector<>();
|
private List<Throwable> errors = new Vector<>();
|
||||||
private StreamUrlIdHandler urlIdHandler;
|
private UrlIdHandler urlIdHandler;
|
||||||
private int serviceId = -1;
|
private int serviceId = -1;
|
||||||
|
|
||||||
public StreamPreviewInfoCollector(StreamUrlIdHandler handler, int serviceId) {
|
public StreamPreviewInfoCollector(UrlIdHandler handler, int serviceId) {
|
||||||
urlIdHandler = handler;
|
urlIdHandler = handler;
|
||||||
this.serviceId = serviceId;
|
this.serviceId = serviceId;
|
||||||
}
|
}
|
||||||
|
@ -40,7 +40,7 @@ public class StreamPreviewInfoCollector {
|
||||||
return itemList;
|
return itemList;
|
||||||
}
|
}
|
||||||
|
|
||||||
public List<Exception> getErrors() {
|
public List<Throwable> getErrors() {
|
||||||
return errors;
|
return errors;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -57,7 +57,7 @@ public class StreamPreviewInfoCollector {
|
||||||
if (urlIdHandler == null) {
|
if (urlIdHandler == null) {
|
||||||
throw new ParsingException("Error: UrlIdHandler not set");
|
throw new ParsingException("Error: UrlIdHandler not set");
|
||||||
} else if(!resultItem.webpage_url.isEmpty()) {
|
} 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.title = extractor.getTitle();
|
||||||
resultItem.stream_type = extractor.getStreamType();
|
resultItem.stream_type = extractor.getStreamType();
|
||||||
|
|
|
@ -24,7 +24,7 @@ public class StreamPreviewInfoSearchCollector extends StreamPreviewInfoCollector
|
||||||
|
|
||||||
private String suggestion = "";
|
private String suggestion = "";
|
||||||
|
|
||||||
public StreamPreviewInfoSearchCollector(StreamUrlIdHandler handler, int serviceId) {
|
public StreamPreviewInfoSearchCollector(UrlIdHandler handler, int serviceId) {
|
||||||
super(handler, serviceId);
|
super(handler, serviceId);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -38,7 +38,10 @@ public abstract class StreamingService {
|
||||||
public abstract StreamExtractor getExtractorInstance(String url, Downloader downloader)
|
public abstract StreamExtractor getExtractorInstance(String url, Downloader downloader)
|
||||||
throws IOException, ExtractionException;
|
throws IOException, ExtractionException;
|
||||||
public abstract SearchEngine getSearchEngineInstance(Downloader downloader);
|
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() {
|
public final int getServiceId() {
|
||||||
return serviceId;
|
return serviceId;
|
||||||
|
|
|
@ -1,10 +1,10 @@
|
||||||
package org.schabi.newpipe.extractor;
|
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>
|
* 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
|
* NewPipe is free software: you can redistribute it and/or modify
|
||||||
* it under the terms of the GNU General Public License as published by
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
@ -20,9 +20,9 @@ package org.schabi.newpipe.extractor;
|
||||||
* along with NewPipe. If not, see <http://www.gnu.org/licenses/>.
|
* along with NewPipe. If not, see <http://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
public interface StreamUrlIdHandler {
|
public interface UrlIdHandler {
|
||||||
String getVideoUrl(String videoId);
|
String getUrl(String videoId);
|
||||||
String getVideoId(String siteUrl) throws ParsingException;
|
String getId(String siteUrl) throws ParsingException;
|
||||||
String cleanUrl(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
|
/**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.StreamPreviewInfoCollector;
|
||||||
import org.schabi.newpipe.extractor.StreamPreviewInfoExtractor;
|
import org.schabi.newpipe.extractor.StreamPreviewInfoExtractor;
|
||||||
import org.schabi.newpipe.extractor.StreamPreviewInfoSearchCollector;
|
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.Node;
|
||||||
import org.w3c.dom.NodeList;
|
import org.w3c.dom.NodeList;
|
||||||
import org.xml.sax.InputSource;
|
import org.xml.sax.InputSource;
|
||||||
|
@ -55,7 +55,7 @@ public class YoutubeSearchEngine extends SearchEngine {
|
||||||
private static final String TAG = YoutubeSearchEngine.class.toString();
|
private static final String TAG = YoutubeSearchEngine.class.toString();
|
||||||
public static final String CHARSET_UTF_8 = "UTF-8";
|
public static final String CHARSET_UTF_8 = "UTF-8";
|
||||||
|
|
||||||
public YoutubeSearchEngine(StreamUrlIdHandler urlIdHandler, int serviceId) {
|
public YoutubeSearchEngine(UrlIdHandler urlIdHandler, int serviceId) {
|
||||||
super(urlIdHandler, serviceId);
|
super(urlIdHandler, serviceId);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -91,7 +91,6 @@ public class YoutubeSearchEngine extends SearchEngine {
|
||||||
site = downloader.download(url);
|
site = downloader.download(url);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
Document doc = Jsoup.parse(site, url);
|
Document doc = Jsoup.parse(site, url);
|
||||||
Element list = doc.select("ol[class=\"item-section\"]").first();
|
Element list = doc.select("ol[class=\"item-section\"]").first();
|
||||||
|
|
||||||
|
|
|
@ -1,10 +1,11 @@
|
||||||
package org.schabi.newpipe.extractor.services.youtube;
|
package org.schabi.newpipe.extractor.services.youtube;
|
||||||
|
|
||||||
|
import org.schabi.newpipe.extractor.ChannelExtractor;
|
||||||
import org.schabi.newpipe.extractor.ExtractionException;
|
import org.schabi.newpipe.extractor.ExtractionException;
|
||||||
import org.schabi.newpipe.extractor.Downloader;
|
import org.schabi.newpipe.extractor.Downloader;
|
||||||
import org.schabi.newpipe.extractor.StreamExtractor;
|
import org.schabi.newpipe.extractor.StreamExtractor;
|
||||||
import org.schabi.newpipe.extractor.StreamingService;
|
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 org.schabi.newpipe.extractor.SearchEngine;
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
|
@ -45,7 +46,7 @@ public class YoutubeService extends StreamingService {
|
||||||
@Override
|
@Override
|
||||||
public StreamExtractor getExtractorInstance(String url, Downloader downloader)
|
public StreamExtractor getExtractorInstance(String url, Downloader downloader)
|
||||||
throws ExtractionException, IOException {
|
throws ExtractionException, IOException {
|
||||||
StreamUrlIdHandler urlIdHandler = new YoutubeStreamUrlIdHandler();
|
UrlIdHandler urlIdHandler = new YoutubeStreamUrlIdHandler();
|
||||||
if(urlIdHandler.acceptUrl(url)) {
|
if(urlIdHandler.acceptUrl(url)) {
|
||||||
return new YoutubeStreamExtractor(urlIdHandler, url, downloader, getServiceId());
|
return new YoutubeStreamExtractor(urlIdHandler, url, downloader, getServiceId());
|
||||||
}
|
}
|
||||||
|
@ -59,7 +60,18 @@ public class YoutubeService extends StreamingService {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public StreamUrlIdHandler getUrlIdHandlerInstance() {
|
public UrlIdHandler getUrlIdHandlerInstance() {
|
||||||
return new YoutubeStreamUrlIdHandler();
|
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.Parser;
|
||||||
import org.schabi.newpipe.extractor.ParsingException;
|
import org.schabi.newpipe.extractor.ParsingException;
|
||||||
import org.schabi.newpipe.extractor.StreamInfo;
|
import org.schabi.newpipe.extractor.StreamInfo;
|
||||||
import org.schabi.newpipe.extractor.StreamPreviewInfo;
|
|
||||||
import org.schabi.newpipe.extractor.StreamPreviewInfoCollector;
|
import org.schabi.newpipe.extractor.StreamPreviewInfoCollector;
|
||||||
import org.schabi.newpipe.extractor.StreamPreviewInfoExtractor;
|
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.StreamExtractor;
|
||||||
import org.schabi.newpipe.extractor.MediaFormat;
|
import org.schabi.newpipe.extractor.MediaFormat;
|
||||||
import org.schabi.newpipe.extractor.VideoStream;
|
import org.schabi.newpipe.extractor.VideoStream;
|
||||||
|
@ -183,12 +182,12 @@ public class YoutubeStreamExtractor extends StreamExtractor {
|
||||||
// cached values
|
// cached values
|
||||||
private static volatile String decryptionCode = "";
|
private static volatile String decryptionCode = "";
|
||||||
|
|
||||||
StreamUrlIdHandler urlidhandler = new YoutubeStreamUrlIdHandler();
|
UrlIdHandler urlidhandler = new YoutubeStreamUrlIdHandler();
|
||||||
String pageUrl = "";
|
String pageUrl = "";
|
||||||
|
|
||||||
private Downloader downloader;
|
private Downloader downloader;
|
||||||
|
|
||||||
public YoutubeStreamExtractor(StreamUrlIdHandler urlIdHandler, String pageUrl,
|
public YoutubeStreamExtractor(UrlIdHandler urlIdHandler, String pageUrl,
|
||||||
Downloader dl, int serviceId)
|
Downloader dl, int serviceId)
|
||||||
throws ExtractionException, IOException {
|
throws ExtractionException, IOException {
|
||||||
super(urlIdHandler ,pageUrl, dl, serviceId);
|
super(urlIdHandler ,pageUrl, dl, serviceId);
|
||||||
|
@ -203,7 +202,7 @@ public class YoutubeStreamExtractor extends StreamExtractor {
|
||||||
// Check if the video is age restricted
|
// Check if the video is age restricted
|
||||||
if (pageContent.contains("<meta property=\"og:restrictions:age")) {
|
if (pageContent.contains("<meta property=\"og:restrictions:age")) {
|
||||||
String videoInfoUrl = GET_VIDEO_INFO_URL.replace("%%video_id%%",
|
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);
|
String videoInfoPageString = downloader.download(videoInfoUrl);
|
||||||
videoInfoPage = Parser.compatParseMap(videoInfoPageString);
|
videoInfoPage = Parser.compatParseMap(videoInfoPageString);
|
||||||
playerUrl = getPlayerUrlFromRestrictedVideo(pageUrl);
|
playerUrl = getPlayerUrlFromRestrictedVideo(pageUrl);
|
||||||
|
@ -286,7 +285,7 @@ public class YoutubeStreamExtractor extends StreamExtractor {
|
||||||
private String getPlayerUrlFromRestrictedVideo(String pageUrl) throws ParsingException {
|
private String getPlayerUrlFromRestrictedVideo(String pageUrl) throws ParsingException {
|
||||||
try {
|
try {
|
||||||
String playerUrl = "";
|
String playerUrl = "";
|
||||||
String videoId = urlidhandler.getVideoId(pageUrl);
|
String videoId = urlidhandler.getId(pageUrl);
|
||||||
String embedUrl = "https://www.youtube.com/embed/" + videoId;
|
String embedUrl = "https://www.youtube.com/embed/" + videoId;
|
||||||
String embedPageContent = downloader.download(embedUrl);
|
String embedPageContent = downloader.download(embedUrl);
|
||||||
//todo: find out if this can be reapaced by Parser.matchGroup1()
|
//todo: find out if this can be reapaced by Parser.matchGroup1()
|
||||||
|
@ -686,6 +685,16 @@ public class YoutubeStreamExtractor extends StreamExtractor {
|
||||||
return pageUrl;
|
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
|
@Override
|
||||||
public StreamInfo.StreamType getStreamType() throws ParsingException {
|
public StreamInfo.StreamType getStreamType() throws ParsingException {
|
||||||
//todo: if implementing livestream support this value should be generated dynamically
|
//todo: if implementing livestream support this value should be generated dynamically
|
||||||
|
|
|
@ -66,8 +66,6 @@ public class YoutubeStreamPreviewInfoExtractor implements StreamPreviewInfoExtra
|
||||||
} else {
|
} else {
|
||||||
throw new ParsingException("Could not get Duration: " + getTitle(), e);
|
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.Parser;
|
||||||
import org.schabi.newpipe.extractor.ParsingException;
|
import org.schabi.newpipe.extractor.ParsingException;
|
||||||
import org.schabi.newpipe.extractor.StreamUrlIdHandler;
|
import org.schabi.newpipe.extractor.UrlIdHandler;
|
||||||
|
|
||||||
import java.io.UnsupportedEncodingException;
|
import java.io.UnsupportedEncodingException;
|
||||||
import java.net.URLDecoder;
|
import java.net.URLDecoder;
|
||||||
|
@ -27,16 +27,16 @@ import java.net.URLDecoder;
|
||||||
* along with NewPipe. If not, see <http://www.gnu.org/licenses/>.
|
* along with NewPipe. If not, see <http://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
public class YoutubeStreamUrlIdHandler implements StreamUrlIdHandler {
|
public class YoutubeStreamUrlIdHandler implements UrlIdHandler {
|
||||||
@SuppressWarnings("WeakerAccess")
|
@SuppressWarnings("WeakerAccess")
|
||||||
@Override
|
@Override
|
||||||
public String getVideoUrl(String videoId) {
|
public String getUrl(String videoId) {
|
||||||
return "https://www.youtube.com/watch?v=" + videoId;
|
return "https://www.youtube.com/watch?v=" + videoId;
|
||||||
}
|
}
|
||||||
|
|
||||||
@SuppressWarnings("WeakerAccess")
|
@SuppressWarnings("WeakerAccess")
|
||||||
@Override
|
@Override
|
||||||
public String getVideoId(String url) throws ParsingException, IllegalArgumentException {
|
public String getId(String url) throws ParsingException, IllegalArgumentException {
|
||||||
if(url.isEmpty())
|
if(url.isEmpty())
|
||||||
{
|
{
|
||||||
throw new IllegalArgumentException("The url parameter should not be empty");
|
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 {
|
public String cleanUrl(String complexUrl) throws ParsingException {
|
||||||
return getVideoUrl(getVideoId(complexUrl));
|
return getUrl(getId(complexUrl));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@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.LayoutInflater;
|
||||||
import android.view.View;
|
import android.view.View;
|
||||||
import android.view.ViewGroup;
|
import android.view.ViewGroup;
|
||||||
import android.widget.ImageView;
|
|
||||||
import android.widget.TextView;
|
|
||||||
|
|
||||||
import com.nostra13.universalimageloader.core.DisplayImageOptions;
|
import com.nostra13.universalimageloader.core.DisplayImageOptions;
|
||||||
import com.nostra13.universalimageloader.core.ImageLoader;
|
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.AbstractVideoInfo;
|
||||||
import org.schabi.newpipe.extractor.StreamPreviewInfo;
|
import org.schabi.newpipe.extractor.StreamPreviewInfo;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Vector;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Created by Christian Schabesberger on 24.10.15.
|
* Created by the-scrabi on 01.08.16.
|
||||||
*
|
|
||||||
* 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/>.
|
|
||||||
*/
|
*/
|
||||||
|
public class InfoListAdapter extends RecyclerView.Adapter<InfoItemHolder> {
|
||||||
|
|
||||||
public class VideoInfoItemViewCreator {
|
public interface OnItemSelectedListener {
|
||||||
private final LayoutInflater inflater;
|
void selected(String url);
|
||||||
|
}
|
||||||
|
|
||||||
|
private Activity activity = null;
|
||||||
|
private View rootView = null;
|
||||||
|
private List<StreamPreviewInfo> streamList = new Vector<>();
|
||||||
private ImageLoader imageLoader = ImageLoader.getInstance();
|
private ImageLoader imageLoader = ImageLoader.getInstance();
|
||||||
private DisplayImageOptions displayImageOptions = new DisplayImageOptions.Builder().cacheInMemory(true).build();
|
private DisplayImageOptions displayImageOptions =
|
||||||
|
new DisplayImageOptions.Builder().cacheInMemory(true).build();
|
||||||
|
private OnItemSelectedListener onItemSelectedListener;
|
||||||
|
|
||||||
public VideoInfoItemViewCreator(LayoutInflater inflater) {
|
|
||||||
this.inflater = inflater;
|
|
||||||
|
public InfoListAdapter(Activity a, View rootView) {
|
||||||
|
activity = a;
|
||||||
|
this.rootView = rootView;
|
||||||
}
|
}
|
||||||
|
|
||||||
public View getViewFromVideoInfoItem(View convertView, ViewGroup parent, StreamPreviewInfo info) {
|
public void setOnItemSelectedListener(OnItemSelectedListener onItemSelectedListener) {
|
||||||
ViewHolder holder;
|
this.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();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// fill with information
|
public void addStreamItemList(List<StreamPreviewInfo> videos) {
|
||||||
|
if(videos!= null) {
|
||||||
/*
|
streamList.addAll(videos);
|
||||||
if(info.thumbnail == null) {
|
notifyDataSetChanged();
|
||||||
holder.itemThumbnailView.setImageResource(R.drawable.dummy_thumbnail);
|
|
||||||
} else {
|
|
||||||
holder.itemThumbnailView.setImageBitmap(info.thumbnail);
|
|
||||||
}
|
}
|
||||||
*/
|
}
|
||||||
|
|
||||||
|
public void clearSteamItemList() {
|
||||||
|
streamList = new Vector<>();
|
||||||
|
notifyDataSetChanged();
|
||||||
|
}
|
||||||
|
|
||||||
|
@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);
|
holder.itemVideoTitleView.setText(info.title);
|
||||||
if(info.uploader != null && !info.uploader.isEmpty()) {
|
if(info.uploader != null && !info.uploader.isEmpty()) {
|
||||||
holder.itemUploaderView.setText(info.uploader);
|
holder.itemUploaderView.setText(info.uploader);
|
||||||
|
@ -94,18 +100,22 @@ public class VideoInfoItemViewCreator {
|
||||||
|
|
||||||
holder.itemThumbnailView.setImageResource(R.drawable.dummy_thumbnail);
|
holder.itemThumbnailView.setImageResource(R.drawable.dummy_thumbnail);
|
||||||
if(info.thumbnail_url != null && !info.thumbnail_url.isEmpty()) {
|
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){
|
if(viewCount >= 1000000000){
|
||||||
return Long.toString(viewCount/1000000000)+"B views";
|
return Long.toString(viewCount/1000000000)+"B views";
|
||||||
}else if(viewCount>=1000000){
|
}else if(viewCount>=1000000){
|
|
@ -23,8 +23,8 @@ import android.widget.Toast;
|
||||||
import org.schabi.newpipe.ActivityCommunicator;
|
import org.schabi.newpipe.ActivityCommunicator;
|
||||||
import org.schabi.newpipe.BuildConfig;
|
import org.schabi.newpipe.BuildConfig;
|
||||||
import org.schabi.newpipe.R;
|
import org.schabi.newpipe.R;
|
||||||
import org.schabi.newpipe.VideoItemDetailActivity;
|
import org.schabi.newpipe.detail.VideoItemDetailActivity;
|
||||||
import org.schabi.newpipe.VideoItemDetailFragment;
|
import org.schabi.newpipe.detail.VideoItemDetailFragment;
|
||||||
|
|
||||||
import java.io.IOException;
|
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.content.Context;
|
||||||
import android.database.Cursor;
|
import android.database.Cursor;
|
||||||
|
@ -9,27 +9,10 @@ import android.view.View;
|
||||||
import android.view.ViewGroup;
|
import android.view.ViewGroup;
|
||||||
import android.widget.TextView;
|
import android.widget.TextView;
|
||||||
|
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Created by Madiyar on 23.02.2016.
|
* Created by the-scrabi on 02.08.16.
|
||||||
*
|
|
||||||
* 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/>.
|
|
||||||
*/
|
*/
|
||||||
|
|
||||||
public class SuggestionListAdapter extends CursorAdapter {
|
public class SuggestionListAdapter extends CursorAdapter {
|
|
@ -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/>.
|
* 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.Context;
|
||||||
import android.content.SharedPreferences;
|
import android.content.SharedPreferences;
|
||||||
|
@ -26,6 +26,8 @@ import android.os.Environment;
|
||||||
import android.preference.PreferenceManager;
|
import android.preference.PreferenceManager;
|
||||||
import android.support.annotation.NonNull;
|
import android.support.annotation.NonNull;
|
||||||
|
|
||||||
|
import org.schabi.newpipe.R;
|
||||||
|
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
|
|
||||||
import us.shandian.giga.util.Utility;
|
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.Context;
|
||||||
import android.content.Intent;
|
import android.content.Intent;
|
||||||
|
@ -14,6 +14,8 @@ import android.view.MenuItem;
|
||||||
import android.view.View;
|
import android.view.View;
|
||||||
import android.view.ViewGroup;
|
import android.view.ViewGroup;
|
||||||
|
|
||||||
|
import org.schabi.newpipe.R;
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Created by Christian Schabesberger on 31.08.15.
|
* 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.app.Activity;
|
||||||
import android.content.ClipData;
|
import android.content.ClipData;
|
||||||
|
@ -16,6 +16,9 @@ import android.preference.PreferenceScreen;
|
||||||
|
|
||||||
import com.nononsenseapps.filepicker.FilePickerActivity;
|
import com.nononsenseapps.filepicker.FilePickerActivity;
|
||||||
|
|
||||||
|
import org.schabi.newpipe.App;
|
||||||
|
import org.schabi.newpipe.R;
|
||||||
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
|
|
||||||
import info.guardianproject.netcipher.proxy.OrbotHelper;
|
import info.guardianproject.netcipher.proxy.OrbotHelper;
|
|
@ -5,7 +5,7 @@ import android.util.Log;
|
||||||
|
|
||||||
import com.google.gson.Gson;
|
import com.google.gson.Gson;
|
||||||
|
|
||||||
import org.schabi.newpipe.NewPipeSettings;
|
import org.schabi.newpipe.settings.NewPipeSettings;
|
||||||
|
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
import java.io.RandomAccessFile;
|
import java.io.RandomAccessFile;
|
||||||
|
|
|
@ -14,7 +14,7 @@ import android.os.Message;
|
||||||
import android.support.v4.app.NotificationCompat.Builder;
|
import android.support.v4.app.NotificationCompat.Builder;
|
||||||
import android.util.Log;
|
import android.util.Log;
|
||||||
|
|
||||||
import org.schabi.newpipe.NewPipeSettings;
|
import org.schabi.newpipe.settings.NewPipeSettings;
|
||||||
import org.schabi.newpipe.R;
|
import org.schabi.newpipe.R;
|
||||||
import us.shandian.giga.get.DownloadManager;
|
import us.shandian.giga.get.DownloadManager;
|
||||||
import us.shandian.giga.get.DownloadManagerImpl;
|
import us.shandian.giga.get.DownloadManagerImpl;
|
||||||
|
|
|
@ -17,9 +17,8 @@ import java.io.IOException;
|
||||||
import java.security.MessageDigest;
|
import java.security.MessageDigest;
|
||||||
import java.security.NoSuchAlgorithmException;
|
import java.security.NoSuchAlgorithmException;
|
||||||
|
|
||||||
import org.schabi.newpipe.NewPipeSettings;
|
import org.schabi.newpipe.settings.NewPipeSettings;
|
||||||
import org.schabi.newpipe.R;
|
import org.schabi.newpipe.R;
|
||||||
import us.shandian.giga.get.DownloadMission;
|
|
||||||
|
|
||||||
import com.nononsenseapps.filepicker.FilePickerActivity;
|
import com.nononsenseapps.filepicker.FilePickerActivity;
|
||||||
import com.nononsenseapps.filepicker.AbstractFilePickerFragment;
|
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"
|
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
xmlns:tools="http://schemas.android.com/tools"
|
xmlns:tools="http://schemas.android.com/tools"
|
||||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||||
tools:context=".VideoItemDetailFragment"
|
tools:context=".detail.VideoItemDetailFragment"
|
||||||
android:textIsSelectable="true"
|
android:textIsSelectable="true"
|
||||||
style="?android:attr/textAppearanceLarge"
|
style="?android:attr/textAppearanceLarge"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="match_parent"
|
android:layout_height="match_parent"
|
||||||
android:id="@+id/videoitem_detail">
|
android:id="@+id/video_item_detail">
|
||||||
|
|
||||||
<com.nirhart.parallaxscroll.views.ParallaxScrollView
|
<com.nirhart.parallaxscroll.views.ParallaxScrollView
|
||||||
android:id="@+id/detailMainContent"
|
android:id="@+id/detail_main_content"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="match_parent"
|
android:layout_height="match_parent"
|
||||||
android:visibility="visible"
|
android:visibility="visible"
|
||||||
|
@ -23,12 +23,12 @@
|
||||||
android:layout_height="wrap_content">
|
android:layout_height="wrap_content">
|
||||||
|
|
||||||
<RelativeLayout
|
<RelativeLayout
|
||||||
android:id="@+id/detailVideoThumbnailWindowLayout"
|
android:id="@+id/detail_stream_thumbnail_window_layout"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:background="?attr/selectableItemBackground">
|
android:background="?attr/selectableItemBackground">
|
||||||
|
|
||||||
<ImageView android:id="@+id/detailThumbnailView"
|
<ImageView android:id="@+id/detail_thumbnail_view"
|
||||||
android:contentDescription="@string/detail_thumbnail_view_description"
|
android:contentDescription="@string/detail_thumbnail_view_description"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
|
@ -40,13 +40,13 @@
|
||||||
android:background="@android:color/black"
|
android:background="@android:color/black"
|
||||||
android:src="@drawable/dummy_thumbnail_dark"/>
|
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_width="wrap_content"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:layout_centerInParent="true"
|
android:layout_centerInParent="true"
|
||||||
android:indeterminate="true"/>
|
android:indeterminate="true"/>
|
||||||
|
|
||||||
<ImageView android:id="@+id/playArrowView"
|
<ImageView android:id="@+id/play_arrow_view"
|
||||||
android:layout_width="wrap_content"
|
android:layout_width="wrap_content"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:background="@android:color/transparent"
|
android:background="@android:color/transparent"
|
||||||
|
@ -55,18 +55,17 @@
|
||||||
android:visibility="invisible"/>
|
android:visibility="invisible"/>
|
||||||
|
|
||||||
<Button
|
<Button
|
||||||
android:id="@+id/detailVideoThumbnailWindowBackgroundButton"
|
android:id="@+id/detail_stream_thumbnail_window_background_button"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="match_parent"
|
android:layout_height="match_parent"
|
||||||
android:background="?attr/selectableItemBackground"/>
|
android:background="?attr/selectableItemBackground"/>
|
||||||
|
|
||||||
</RelativeLayout>
|
</RelativeLayout>
|
||||||
|
|
||||||
<RelativeLayout android:id="@+id/detailTextContentLayout"
|
<RelativeLayout android:id="@+id/detail_text_content_layout"
|
||||||
|
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="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:background="@color/light_background_color"
|
||||||
android:visibility="gone">
|
android:visibility="gone">
|
||||||
|
|
||||||
|
@ -75,7 +74,7 @@
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:id="@+id/detailTopView">
|
android:id="@+id/detailTopView">
|
||||||
|
|
||||||
<TextView android:id="@+id/detailVideoTitleView"
|
<TextView android:id="@+id/detail_video_title_view"
|
||||||
android:layout_width="0dp"
|
android:layout_width="0dp"
|
||||||
android:layout_weight=".7"
|
android:layout_weight=".7"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
|
@ -89,7 +88,7 @@
|
||||||
<ImageView
|
<ImageView
|
||||||
android:layout_width="15dp"
|
android:layout_width="15dp"
|
||||||
android:layout_height="30dp"
|
android:layout_height="30dp"
|
||||||
android:id="@+id/toggleDescriptionView"
|
android:id="@+id/toggle_description_view"
|
||||||
android:src="@drawable/arrow_down"
|
android:src="@drawable/arrow_down"
|
||||||
android:layout_marginLeft="10dp"
|
android:layout_marginLeft="10dp"
|
||||||
android:layout_marginStart="10dp"
|
android:layout_marginStart="10dp"
|
||||||
|
@ -99,7 +98,7 @@
|
||||||
|
|
||||||
</LinearLayout>
|
</LinearLayout>
|
||||||
|
|
||||||
<TextView android:id="@+id/detailViewCountView"
|
<TextView android:id="@+id/detail_view_count_view"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:textSize="@dimen/video_item_detail_views_text_size"
|
android:textSize="@dimen/video_item_detail_views_text_size"
|
||||||
|
@ -115,14 +114,14 @@
|
||||||
<RelativeLayout
|
<RelativeLayout
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:layout_below="@id/detailViewCountView"
|
android:layout_below="@id/detail_view_count_view"
|
||||||
android:id="@+id/detailExtraView"
|
android:id="@+id/detailExtraView"
|
||||||
android:layout_marginLeft="12dp"
|
android:layout_marginLeft="12dp"
|
||||||
android:layout_marginStart="12dp"
|
android:layout_marginStart="12dp"
|
||||||
android:layout_marginRight="12dp"
|
android:layout_marginRight="12dp"
|
||||||
android:layout_marginEnd="12dp"
|
android:layout_marginEnd="12dp"
|
||||||
android:visibility="gone">
|
android:visibility="gone">
|
||||||
<TextView android:id="@+id/detailUploadDateView"
|
<TextView android:id="@+id/detail_upload_date_view"
|
||||||
android:layout_width="wrap_content"
|
android:layout_width="wrap_content"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:textSize="@dimen/video_item_detail_upload_date_text_size"
|
android:textSize="@dimen/video_item_detail_upload_date_text_size"
|
||||||
|
@ -130,12 +129,12 @@
|
||||||
android:text="Upload date"
|
android:text="Upload date"
|
||||||
android:layout_marginTop="3dp" />
|
android:layout_marginTop="3dp" />
|
||||||
|
|
||||||
<TextView android:id="@+id/detailDescriptionView"
|
<TextView android:id="@+id/detail_description_view"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:textSize="@dimen/video_item_detail_description_text_size"
|
android:textSize="@dimen/video_item_detail_description_text_size"
|
||||||
android:textAppearance="?android:attr/textAppearanceMedium"
|
android:textAppearance="?android:attr/textAppearanceMedium"
|
||||||
android:layout_below="@id/detailUploadDateView"
|
android:layout_below="@id/detail_upload_date_view"
|
||||||
android:text="Description............."
|
android:text="Description............."
|
||||||
android:layout_marginTop="3dp" />
|
android:layout_marginTop="3dp" />
|
||||||
</RelativeLayout>
|
</RelativeLayout>
|
||||||
|
@ -143,27 +142,27 @@
|
||||||
<LinearLayout
|
<LinearLayout
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:id="@+id/linearLayout"
|
android:id="@+id/stream_info_layout"
|
||||||
android:layout_marginLeft="12dp"
|
android:layout_marginLeft="12dp"
|
||||||
android:layout_marginStart="12dp"
|
android:layout_marginStart="12dp"
|
||||||
android:layout_below="@+id/detailExtraView"
|
android:layout_below="@+id/detailExtraView"
|
||||||
android:layout_alignParentRight="true"
|
android:layout_alignParentRight="true"
|
||||||
android:layout_alignParentEnd="true"
|
android:layout_alignParentEnd="true"
|
||||||
android:layout_marginTop="5dp">
|
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:contentDescription="@string/detail_likes_img_view_description"
|
||||||
android:layout_width="@dimen/video_item_detail_like_image_width"
|
android:layout_width="@dimen/video_item_detail_like_image_width"
|
||||||
android:layout_height="@dimen/video_item_detail_like_image_height"
|
android:layout_height="@dimen/video_item_detail_like_image_height"
|
||||||
android:src="@drawable/thumbs_up" />
|
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_width="wrap_content"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:textSize="@dimen/video_item_detail_likes_text_size"
|
android:textSize="@dimen/video_item_detail_likes_text_size"
|
||||||
android:textAppearance="?android:attr/textAppearanceMedium"
|
android:textAppearance="?android:attr/textAppearanceMedium"
|
||||||
android:text="200" />
|
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:contentDescription="@string/detail_dislikes_img_view_description"
|
||||||
android:layout_width="@dimen/video_item_detail_like_image_width"
|
android:layout_width="@dimen/video_item_detail_like_image_width"
|
||||||
android:layout_height="@dimen/video_item_detail_like_image_height"
|
android:layout_height="@dimen/video_item_detail_like_image_height"
|
||||||
|
@ -171,7 +170,7 @@
|
||||||
android:layout_marginLeft="10dp"
|
android:layout_marginLeft="10dp"
|
||||||
android:layout_marginStart="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_width="wrap_content"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:textSize="@dimen/video_item_detail_likes_text_size"
|
android:textSize="@dimen/video_item_detail_likes_text_size"
|
||||||
|
@ -179,11 +178,15 @@
|
||||||
android:text="100" />
|
android:text="100" />
|
||||||
</LinearLayout>
|
</LinearLayout>
|
||||||
|
|
||||||
|
<FrameLayout
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:id="@+id/detail_uploader_frame"
|
||||||
|
android:layout_below="@id/stream_info_layout">
|
||||||
<RelativeLayout
|
<RelativeLayout
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:layout_below="@+id/linearLayout"
|
android:id="@+id/detail_uploader_layout"
|
||||||
android:id="@+id/detailUploaderWrapView"
|
|
||||||
android:layout_marginTop="12dp">
|
android:layout_marginTop="12dp">
|
||||||
|
|
||||||
<View
|
<View
|
||||||
|
@ -191,7 +194,8 @@
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="1px" />
|
android:layout_height="1px" />
|
||||||
|
|
||||||
<de.hdodenhof.circleimageview.CircleImageView android:id="@+id/detailUploaderThumbnailView"
|
<de.hdodenhof.circleimageview.CircleImageView
|
||||||
|
android:id="@+id/detail_uploader_thumbnail_view"
|
||||||
android:contentDescription="@string/detail_uploader_thumbnail_view_description"
|
android:contentDescription="@string/detail_uploader_thumbnail_view_description"
|
||||||
android:layout_width="@dimen/video_item_detail_uploader_image_size"
|
android:layout_width="@dimen/video_item_detail_uploader_image_size"
|
||||||
android:layout_height="@dimen/video_item_detail_uploader_image_size"
|
android:layout_height="@dimen/video_item_detail_uploader_image_size"
|
||||||
|
@ -203,7 +207,7 @@
|
||||||
android:layout_marginTop="5dp"
|
android:layout_marginTop="5dp"
|
||||||
android:layout_marginBottom="5dp"/>
|
android:layout_marginBottom="5dp"/>
|
||||||
|
|
||||||
<TextView android:id="@+id/detailUploaderView"
|
<TextView android:id="@+id/detail_uploader_view"
|
||||||
android:layout_width="wrap_content"
|
android:layout_width="wrap_content"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:textStyle="bold"
|
android:textStyle="bold"
|
||||||
|
@ -211,8 +215,8 @@
|
||||||
android:textAppearance="?android:attr/textAppearanceLarge"
|
android:textAppearance="?android:attr/textAppearanceLarge"
|
||||||
android:text="Uploader"
|
android:text="Uploader"
|
||||||
android:layout_centerVertical="true"
|
android:layout_centerVertical="true"
|
||||||
android:layout_toRightOf="@+id/detailUploaderThumbnailView"
|
android:layout_toRightOf="@+id/detail_uploader_thumbnail_view"
|
||||||
android:layout_toEndOf="@+id/detailUploaderThumbnailView"
|
android:layout_toEndOf="@+id/detail_uploader_thumbnail_view"
|
||||||
android:layout_marginLeft="15dp"
|
android:layout_marginLeft="15dp"
|
||||||
android:layout_marginStart="28dp" />
|
android:layout_marginStart="28dp" />
|
||||||
|
|
||||||
|
@ -220,18 +224,26 @@
|
||||||
android:background="#000"
|
android:background="#000"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="1px"
|
android:layout_height="1px"
|
||||||
android:layout_below="@id/detailUploaderThumbnailView"/>
|
android:layout_below="@id/detail_uploader_thumbnail_view"/>
|
||||||
</RelativeLayout>
|
</RelativeLayout>
|
||||||
|
<Button
|
||||||
|
android:layout_marginTop="13dp"
|
||||||
|
android:id="@+id/channel_button"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
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_width="match_parent"
|
||||||
android:layout_height="match_parent"
|
android:layout_height="match_parent"
|
||||||
android:layout_gravity="center_horizontal|bottom"
|
android:layout_gravity="center_horizontal|bottom"
|
||||||
android:layout_below="@+id/detailUploaderWrapView"
|
android:layout_below="@+id/detail_uploader_frame"
|
||||||
android:layout_centerHorizontal="true"
|
android:layout_centerHorizontal="true"
|
||||||
android:layout_marginTop="10dp">
|
android:layout_marginTop="10dp">
|
||||||
|
|
||||||
<TextView android:id="@+id/detailNextVideoTitle"
|
<TextView
|
||||||
|
android:id="@+id/detail_next_stream_title"
|
||||||
android:layout_width="wrap_content"
|
android:layout_width="wrap_content"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:layout_centerHorizontal="true"
|
android:layout_centerHorizontal="true"
|
||||||
|
@ -240,38 +252,25 @@
|
||||||
android:text="@string/next_video_title"
|
android:text="@string/next_video_title"
|
||||||
android:textAllCaps="true" />
|
android:textAllCaps="true" />
|
||||||
|
|
||||||
<RelativeLayout android:id="@+id/detailNextVidButtonAndContentLayout"
|
<android.support.v7.widget.RecyclerView
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="match_parent"
|
android:layout_height="wrap_content"
|
||||||
android:layout_below="@id/detailNextVideoTitle">
|
android:id="@+id/detail_next_stream_content"/>
|
||||||
<FrameLayout
|
<TextView android:id="@+id/detail_similar_title"
|
||||||
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_width="wrap_content"
|
android:layout_width="wrap_content"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:layout_centerHorizontal="true"
|
android:layout_centerHorizontal="true"
|
||||||
android:textSize="@dimen/video_item_detail_next_text_size"
|
android:textSize="@dimen/video_item_detail_next_text_size"
|
||||||
android:textAppearance="?android:attr/textAppearanceMedium"
|
android:textAppearance="?android:attr/textAppearanceMedium"
|
||||||
android:text="@string/similar_videos_btn_text"
|
android:text="@string/similar_videos_btn_text"
|
||||||
android:layout_below="@id/detailNextVidButtonAndContentLayout"
|
android:layout_below="@id/detail_next_stream_content"
|
||||||
android:textAllCaps="true" />
|
android:textAllCaps="true" />
|
||||||
<LinearLayout
|
<android.support.v7.widget.RecyclerView
|
||||||
android:id="@+id/similarVideosView"
|
android:id="@+id/similar_streams_view"
|
||||||
android:orientation="vertical"
|
android:orientation="vertical"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="match_parent"
|
android:layout_height="match_parent"
|
||||||
android:layout_below="@id/detailSimilarTitle">
|
android:layout_below="@id/detail_similar_title"/>
|
||||||
</LinearLayout>
|
|
||||||
</RelativeLayout>
|
</RelativeLayout>
|
||||||
</RelativeLayout>
|
</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"
|
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
xmlns:tools="http://schemas.android.com/tools" android:id="@+id/videoitem_detail_container"
|
xmlns:tools="http://schemas.android.com/tools" android:id="@+id/videoitem_detail_container"
|
||||||
android:layout_width="match_parent" android:layout_height="match_parent"
|
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"
|
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
xmlns:tools="http://schemas.android.com/tools"
|
xmlns:tools="http://schemas.android.com/tools"
|
||||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||||
tools:context=".VideoItemDetailFragment"
|
tools:context=".detail.VideoItemDetailFragment"
|
||||||
android:textIsSelectable="true"
|
android:textIsSelectable="true"
|
||||||
style="?android:attr/textAppearanceLarge"
|
style="?android:attr/textAppearanceLarge"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="match_parent"
|
android:layout_height="match_parent"
|
||||||
android:id="@+id/videoitem_detail">
|
android:id="@+id/video_item_detail">
|
||||||
|
|
||||||
<com.nirhart.parallaxscroll.views.ParallaxScrollView
|
<com.nirhart.parallaxscroll.views.ParallaxScrollView
|
||||||
android:id="@+id/detailMainContent"
|
android:id="@+id/detail_main_content"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="match_parent"
|
android:layout_height="match_parent"
|
||||||
android:visibility="visible"
|
android:visibility="visible"
|
||||||
|
@ -23,12 +23,12 @@
|
||||||
android:layout_height="wrap_content">
|
android:layout_height="wrap_content">
|
||||||
|
|
||||||
<RelativeLayout
|
<RelativeLayout
|
||||||
android:id="@+id/detailVideoThumbnailWindowLayout"
|
android:id="@+id/detail_stream_thumbnail_window_layout"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:background="?attr/selectableItemBackground">
|
android:background="?attr/selectableItemBackground">
|
||||||
|
|
||||||
<ImageView android:id="@+id/detailThumbnailView"
|
<ImageView android:id="@+id/detail_thumbnail_view"
|
||||||
android:contentDescription="@string/detail_thumbnail_view_description"
|
android:contentDescription="@string/detail_thumbnail_view_description"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
|
@ -40,14 +40,14 @@
|
||||||
android:background="@android:color/black"
|
android:background="@android:color/black"
|
||||||
android:src="@drawable/dummy_thumbnail_dark"/>
|
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_width="wrap_content"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:layout_centerInParent="true"
|
android:layout_centerInParent="true"
|
||||||
android:indeterminate="true"/>
|
android:indeterminate="true"/>
|
||||||
|
|
||||||
<android.support.design.widget.FloatingActionButton
|
<android.support.design.widget.FloatingActionButton
|
||||||
android:id="@+id/playVideoButton"
|
android:id="@+id/play_video_button"
|
||||||
android:visibility="invisible"
|
android:visibility="invisible"
|
||||||
android:layout_width="wrap_content"
|
android:layout_width="wrap_content"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
|
@ -57,18 +57,17 @@
|
||||||
android:layout_margin="@dimen/video_item_detail_play_fab_margin"/>
|
android:layout_margin="@dimen/video_item_detail_play_fab_margin"/>
|
||||||
|
|
||||||
<Button
|
<Button
|
||||||
android:id="@+id/detailVideoThumbnailWindowBackgroundButton"
|
android:id="@+id/detail_stream_thumbnail_window_background_button"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="match_parent"
|
android:layout_height="match_parent"
|
||||||
android:background="?attr/selectableItemBackground"/>
|
android:background="?attr/selectableItemBackground"/>
|
||||||
|
|
||||||
</RelativeLayout>
|
</RelativeLayout>
|
||||||
|
|
||||||
<RelativeLayout android:id="@+id/detailTextContentLayout"
|
<RelativeLayout android:id="@+id/detail_text_content_layout"
|
||||||
|
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="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:background="@color/light_background_color"
|
||||||
android:visibility="gone">
|
android:visibility="gone">
|
||||||
|
|
||||||
|
@ -77,7 +76,7 @@
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:id="@+id/detailTopView">
|
android:id="@+id/detailTopView">
|
||||||
|
|
||||||
<TextView android:id="@+id/detailVideoTitleView"
|
<TextView android:id="@+id/detail_video_title_view"
|
||||||
android:layout_width="0dp"
|
android:layout_width="0dp"
|
||||||
android:layout_weight=".7"
|
android:layout_weight=".7"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
|
@ -91,7 +90,7 @@
|
||||||
<ImageView
|
<ImageView
|
||||||
android:layout_width="15dp"
|
android:layout_width="15dp"
|
||||||
android:layout_height="30dp"
|
android:layout_height="30dp"
|
||||||
android:id="@+id/toggleDescriptionView"
|
android:id="@+id/toggle_description_view"
|
||||||
android:src="@drawable/arrow_down"
|
android:src="@drawable/arrow_down"
|
||||||
android:layout_marginLeft="10dp"
|
android:layout_marginLeft="10dp"
|
||||||
android:layout_marginStart="10dp"
|
android:layout_marginStart="10dp"
|
||||||
|
@ -101,7 +100,7 @@
|
||||||
|
|
||||||
</LinearLayout>
|
</LinearLayout>
|
||||||
|
|
||||||
<TextView android:id="@+id/detailViewCountView"
|
<TextView android:id="@+id/detail_view_count_view"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:textSize="@dimen/video_item_detail_views_text_size"
|
android:textSize="@dimen/video_item_detail_views_text_size"
|
||||||
|
@ -117,14 +116,14 @@
|
||||||
<RelativeLayout
|
<RelativeLayout
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:layout_below="@id/detailViewCountView"
|
android:layout_below="@id/detail_view_count_view"
|
||||||
android:id="@+id/detailExtraView"
|
android:id="@+id/detailExtraView"
|
||||||
android:layout_marginLeft="12dp"
|
android:layout_marginLeft="12dp"
|
||||||
android:layout_marginStart="12dp"
|
android:layout_marginStart="12dp"
|
||||||
android:layout_marginRight="12dp"
|
android:layout_marginRight="12dp"
|
||||||
android:layout_marginEnd="12dp"
|
android:layout_marginEnd="12dp"
|
||||||
android:visibility="gone">
|
android:visibility="gone">
|
||||||
<TextView android:id="@+id/detailUploadDateView"
|
<TextView android:id="@+id/detail_upload_date_view"
|
||||||
android:layout_width="wrap_content"
|
android:layout_width="wrap_content"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:textSize="@dimen/video_item_detail_upload_date_text_size"
|
android:textSize="@dimen/video_item_detail_upload_date_text_size"
|
||||||
|
@ -132,12 +131,12 @@
|
||||||
android:text="Upload date"
|
android:text="Upload date"
|
||||||
android:layout_marginTop="3dp" />
|
android:layout_marginTop="3dp" />
|
||||||
|
|
||||||
<TextView android:id="@+id/detailDescriptionView"
|
<TextView android:id="@+id/detail_description_view"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:textSize="@dimen/video_item_detail_description_text_size"
|
android:textSize="@dimen/video_item_detail_description_text_size"
|
||||||
android:textAppearance="?android:attr/textAppearanceMedium"
|
android:textAppearance="?android:attr/textAppearanceMedium"
|
||||||
android:layout_below="@id/detailUploadDateView"
|
android:layout_below="@id/detail_upload_date_view"
|
||||||
android:text="Description............."
|
android:text="Description............."
|
||||||
android:layout_marginTop="3dp" />
|
android:layout_marginTop="3dp" />
|
||||||
</RelativeLayout>
|
</RelativeLayout>
|
||||||
|
@ -145,27 +144,27 @@
|
||||||
<LinearLayout
|
<LinearLayout
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:id="@+id/linearLayout"
|
android:id="@+id/stream_info_layout"
|
||||||
android:layout_marginLeft="12dp"
|
android:layout_marginLeft="12dp"
|
||||||
android:layout_marginStart="12dp"
|
android:layout_marginStart="12dp"
|
||||||
android:layout_below="@+id/detailExtraView"
|
android:layout_below="@+id/detailExtraView"
|
||||||
android:layout_alignParentRight="true"
|
android:layout_alignParentRight="true"
|
||||||
android:layout_alignParentEnd="true"
|
android:layout_alignParentEnd="true"
|
||||||
android:layout_marginTop="5dp">
|
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:contentDescription="@string/detail_likes_img_view_description"
|
||||||
android:layout_width="@dimen/video_item_detail_like_image_width"
|
android:layout_width="@dimen/video_item_detail_like_image_width"
|
||||||
android:layout_height="@dimen/video_item_detail_like_image_height"
|
android:layout_height="@dimen/video_item_detail_like_image_height"
|
||||||
android:src="@drawable/thumbs_up" />
|
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_width="wrap_content"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:textSize="@dimen/video_item_detail_likes_text_size"
|
android:textSize="@dimen/video_item_detail_likes_text_size"
|
||||||
android:textAppearance="?android:attr/textAppearanceMedium"
|
android:textAppearance="?android:attr/textAppearanceMedium"
|
||||||
android:text="200" />
|
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:contentDescription="@string/detail_dislikes_img_view_description"
|
||||||
android:layout_width="@dimen/video_item_detail_like_image_width"
|
android:layout_width="@dimen/video_item_detail_like_image_width"
|
||||||
android:layout_height="@dimen/video_item_detail_like_image_height"
|
android:layout_height="@dimen/video_item_detail_like_image_height"
|
||||||
|
@ -173,7 +172,7 @@
|
||||||
android:layout_marginLeft="10dp"
|
android:layout_marginLeft="10dp"
|
||||||
android:layout_marginStart="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_width="wrap_content"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:textSize="@dimen/video_item_detail_likes_text_size"
|
android:textSize="@dimen/video_item_detail_likes_text_size"
|
||||||
|
@ -181,11 +180,15 @@
|
||||||
android:text="100" />
|
android:text="100" />
|
||||||
</LinearLayout>
|
</LinearLayout>
|
||||||
|
|
||||||
|
<FrameLayout
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:id="@+id/detail_uploader_frame"
|
||||||
|
android:layout_below="@id/stream_info_layout">
|
||||||
<RelativeLayout
|
<RelativeLayout
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:layout_below="@+id/linearLayout"
|
android:id="@+id/detail_uploader_layout"
|
||||||
android:id="@+id/detailUploaderWrapView"
|
|
||||||
android:layout_marginTop="12dp">
|
android:layout_marginTop="12dp">
|
||||||
|
|
||||||
<View
|
<View
|
||||||
|
@ -193,7 +196,8 @@
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="1px" />
|
android:layout_height="1px" />
|
||||||
|
|
||||||
<de.hdodenhof.circleimageview.CircleImageView android:id="@+id/detailUploaderThumbnailView"
|
<de.hdodenhof.circleimageview.CircleImageView
|
||||||
|
android:id="@+id/detail_uploader_thumbnail_view"
|
||||||
android:contentDescription="@string/detail_uploader_thumbnail_view_description"
|
android:contentDescription="@string/detail_uploader_thumbnail_view_description"
|
||||||
android:layout_width="@dimen/video_item_detail_uploader_image_size"
|
android:layout_width="@dimen/video_item_detail_uploader_image_size"
|
||||||
android:layout_height="@dimen/video_item_detail_uploader_image_size"
|
android:layout_height="@dimen/video_item_detail_uploader_image_size"
|
||||||
|
@ -205,7 +209,7 @@
|
||||||
android:layout_marginTop="5dp"
|
android:layout_marginTop="5dp"
|
||||||
android:layout_marginBottom="5dp"/>
|
android:layout_marginBottom="5dp"/>
|
||||||
|
|
||||||
<TextView android:id="@+id/detailUploaderView"
|
<TextView android:id="@+id/detail_uploader_view"
|
||||||
android:layout_width="wrap_content"
|
android:layout_width="wrap_content"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:textStyle="bold"
|
android:textStyle="bold"
|
||||||
|
@ -213,8 +217,8 @@
|
||||||
android:textAppearance="?android:attr/textAppearanceLarge"
|
android:textAppearance="?android:attr/textAppearanceLarge"
|
||||||
android:text="Uploader"
|
android:text="Uploader"
|
||||||
android:layout_centerVertical="true"
|
android:layout_centerVertical="true"
|
||||||
android:layout_toRightOf="@+id/detailUploaderThumbnailView"
|
android:layout_toRightOf="@+id/detail_uploader_thumbnail_view"
|
||||||
android:layout_toEndOf="@+id/detailUploaderThumbnailView"
|
android:layout_toEndOf="@+id/detail_uploader_thumbnail_view"
|
||||||
android:layout_marginLeft="15dp"
|
android:layout_marginLeft="15dp"
|
||||||
android:layout_marginStart="28dp" />
|
android:layout_marginStart="28dp" />
|
||||||
|
|
||||||
|
@ -222,18 +226,26 @@
|
||||||
android:background="#000"
|
android:background="#000"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="1px"
|
android:layout_height="1px"
|
||||||
android:layout_below="@id/detailUploaderThumbnailView"/>
|
android:layout_below="@id/detail_uploader_thumbnail_view"/>
|
||||||
</RelativeLayout>
|
</RelativeLayout>
|
||||||
|
<Button
|
||||||
|
android:layout_marginTop="13dp"
|
||||||
|
android:id="@+id/channel_button"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
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_width="match_parent"
|
||||||
android:layout_height="match_parent"
|
android:layout_height="match_parent"
|
||||||
android:layout_gravity="center_horizontal|bottom"
|
android:layout_gravity="center_horizontal|bottom"
|
||||||
android:layout_below="@+id/detailUploaderWrapView"
|
android:layout_below="@+id/detail_uploader_frame"
|
||||||
android:layout_centerHorizontal="true"
|
android:layout_centerHorizontal="true"
|
||||||
android:layout_marginTop="10dp">
|
android:layout_marginTop="10dp">
|
||||||
|
|
||||||
<TextView android:id="@+id/detailNextVideoTitle"
|
<TextView
|
||||||
|
android:id="@+id/detail_next_stream_title"
|
||||||
android:layout_width="wrap_content"
|
android:layout_width="wrap_content"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:layout_centerHorizontal="true"
|
android:layout_centerHorizontal="true"
|
||||||
|
@ -242,38 +254,25 @@
|
||||||
android:text="@string/next_video_title"
|
android:text="@string/next_video_title"
|
||||||
android:textAllCaps="true" />
|
android:textAllCaps="true" />
|
||||||
|
|
||||||
<RelativeLayout android:id="@+id/detailNextVidButtonAndContentLayout"
|
<android.support.v7.widget.RecyclerView
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="match_parent"
|
android:layout_height="wrap_content"
|
||||||
android:layout_below="@id/detailNextVideoTitle">
|
android:id="@+id/detail_next_stream_content"/>
|
||||||
<FrameLayout
|
<TextView android:id="@+id/detail_similar_title"
|
||||||
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_width="wrap_content"
|
android:layout_width="wrap_content"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:layout_centerHorizontal="true"
|
android:layout_centerHorizontal="true"
|
||||||
android:textSize="@dimen/video_item_detail_next_text_size"
|
android:textSize="@dimen/video_item_detail_next_text_size"
|
||||||
android:textAppearance="?android:attr/textAppearanceMedium"
|
android:textAppearance="?android:attr/textAppearanceMedium"
|
||||||
android:text="@string/similar_videos_btn_text"
|
android:text="@string/similar_videos_btn_text"
|
||||||
android:layout_below="@id/detailNextVidButtonAndContentLayout"
|
android:layout_below="@id/detail_next_stream_content"
|
||||||
android:textAllCaps="true" />
|
android:textAllCaps="true" />
|
||||||
<LinearLayout
|
<android.support.v7.widget.RecyclerView
|
||||||
|
android:id="@+id/similar_streams_view"
|
||||||
android:orientation="vertical"
|
android:orientation="vertical"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="match_parent"
|
android:layout_height="match_parent"
|
||||||
android:id="@+id/similarVideosView"
|
android:layout_below="@id/detail_similar_title"/>
|
||||||
android:layout_below="@id/detailSimilarTitle">
|
|
||||||
</LinearLayout>
|
|
||||||
</RelativeLayout>
|
</RelativeLayout>
|
||||||
</RelativeLayout>
|
</RelativeLayout>
|
||||||
</RelativeLayout>
|
</RelativeLayout>
|
||||||
|
|
|
@ -4,8 +4,7 @@
|
||||||
android:orientation="vertical" android:layout_width="match_parent"
|
android:orientation="vertical" android:layout_width="match_parent"
|
||||||
android:layout_height="match_parent"
|
android:layout_height="match_parent"
|
||||||
android:id="@+id/mainBG"
|
android:id="@+id/mainBG"
|
||||||
tools:context=".VideoItemDetailActivity"
|
tools:context=".detail.VideoItemDetailActivity">
|
||||||
tools:showIn="@layout/activity_videoitem_list">
|
|
||||||
|
|
||||||
<TextView
|
<TextView
|
||||||
android:layout_width="wrap_content"
|
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"?>
|
<?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"
|
xmlns:tools="http://schemas.android.com/tools"
|
||||||
android:layout_width="match_parent"
|
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
|
<RelativeLayout
|
||||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
@ -93,3 +99,10 @@
|
||||||
|
|
||||||
</RelativeLayout>
|
</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>
|
|
@ -35,4 +35,11 @@
|
||||||
<item name="background">@color/video_overlay_color</item>
|
<item name="background">@color/video_overlay_color</item>
|
||||||
</style>
|
</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>
|
</resources>
|
|
@ -31,11 +31,16 @@
|
||||||
<dimen name="video_item_detail_uploader_image_size">50dp</dimen>
|
<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_height">18sp</dimen>
|
||||||
<dimen name="video_item_detail_like_image_width">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 -->
|
<!-- Paddings & Margins -->
|
||||||
<dimen name="video_item_detail_like_margin">6sp</dimen>
|
<dimen name="video_item_detail_like_margin">6sp</dimen>
|
||||||
<dimen name="video_item_detail_play_fab_margin">20dp</dimen>
|
<dimen name="video_item_detail_play_fab_margin">20dp</dimen>
|
||||||
<!-- Default screen margins, per the Android Design guidelines. -->
|
<!-- Default screen margins, per the Android Design guidelines. -->
|
||||||
<dimen name="activity_horizontal_margin">16dp</dimen>
|
<dimen name="activity_horizontal_margin">16dp</dimen>
|
||||||
<dimen name="activity_vertical_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>
|
</resources>
|
||||||
|
|
|
@ -96,6 +96,7 @@
|
||||||
<string name="could_not_setup_download_menu">Could not setup download menu.</string>
|
<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="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_get_stream">Could not get any stream.</string>
|
||||||
|
<string name="could_not_load_image">Could not load Image</string>
|
||||||
<!-- error activity -->
|
<!-- error activity -->
|
||||||
<string name="sorry_string">Sorry that should not happen.</string>
|
<string name="sorry_string">Sorry that should not happen.</string>
|
||||||
<string name="guru_meditation" translatable="false">Guru Meditation.</string>
|
<string name="guru_meditation" translatable="false">Guru Meditation.</string>
|
||||||
|
@ -176,6 +177,97 @@
|
||||||
<!-- Checksum types -->
|
<!-- Checksum types -->
|
||||||
<string name="md5" translatable="false">MD5</string>
|
<string name="md5" translatable="false">MD5</string>
|
||||||
<string name="sha1" translatable="false">SHA1</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 -->
|
<!-- End of GigaGet's Strings -->
|
||||||
|
|
||||||
|
|
|
@ -1,7 +1,6 @@
|
||||||
<resources>
|
<resources>
|
||||||
|
|
||||||
<style name="RootTheme" parent="android:Theme.Holo">
|
<style name="RootTheme" parent="android:Theme.Holo"></style>
|
||||||
</style>
|
|
||||||
|
|
||||||
<style name="PlayerTheme" parent="@style/RootTheme">
|
<style name="PlayerTheme" parent="@style/RootTheme">
|
||||||
<item name="android:windowNoTitle">true</item>
|
<item name="android:windowNoTitle">true</item>
|
||||||
|
@ -72,4 +71,13 @@
|
||||||
<item name="colorAccent">@color/light_youtube_accent_color</item>
|
<item name="colorAccent">@color/light_youtube_accent_color</item>
|
||||||
</style>
|
</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>
|
</resources>
|
||||||
|
|
|
@ -5,7 +5,7 @@ buildscript {
|
||||||
jcenter()
|
jcenter()
|
||||||
}
|
}
|
||||||
dependencies {
|
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
|
// NOTE: Do not place your application dependencies here; they belong
|
||||||
// in the individual module build.gradle files
|
// 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
|
distributionBase=GRADLE_USER_HOME
|
||||||
distributionPath=wrapper/dists
|
distributionPath=wrapper/dists
|
||||||
zipStoreBase=GRADLE_USER_HOME
|
zipStoreBase=GRADLE_USER_HOME
|
||||||
zipStorePath=wrapper/dists
|
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