mirror of
https://github.com/TeamNewPipe/NewPipe
synced 2024-12-24 09:00:31 +00:00
add basics
This commit is contained in:
parent
c5583bd77b
commit
9a0f61e60b
@ -80,10 +80,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"
|
||||||
@ -102,10 +104,12 @@
|
|||||||
<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=".SettingsActivity"
|
||||||
android:label="@string/settings_activity_title" />
|
android:label="@string/settings_activity_title" />
|
||||||
@ -130,19 +134,20 @@
|
|||||||
<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>
|
||||||
</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"
|
||||||
android:label="@string/app_name"
|
android:label="@string/app_name"
|
||||||
android:theme="@style/FilePickerTheme"
|
android:launchMode="singleTask"
|
||||||
android:launchMode="singleTask">
|
android:theme="@style/FilePickerTheme"></activity>
|
||||||
</activity>
|
<activity
|
||||||
|
android:name=".ChannelActivity"
|
||||||
|
android:label="@string/title_activity_channel"
|
||||||
|
android:theme="@style/AppTheme.NoActionBar"></activity>
|
||||||
</application>
|
</application>
|
||||||
|
|
||||||
</manifest>
|
</manifest>
|
28
app/src/main/java/org/schabi/newpipe/ChannelActivity.java
Normal file
28
app/src/main/java/org/schabi/newpipe/ChannelActivity.java
Normal file
@ -0,0 +1,28 @@
|
|||||||
|
package org.schabi.newpipe;
|
||||||
|
|
||||||
|
import android.os.Bundle;
|
||||||
|
import android.support.design.widget.FloatingActionButton;
|
||||||
|
import android.support.design.widget.Snackbar;
|
||||||
|
import android.support.v7.app.AppCompatActivity;
|
||||||
|
import android.support.v7.widget.Toolbar;
|
||||||
|
import android.view.View;
|
||||||
|
|
||||||
|
public class ChannelActivity extends AppCompatActivity {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void onCreate(Bundle savedInstanceState) {
|
||||||
|
super.onCreate(savedInstanceState);
|
||||||
|
setContentView(R.layout.activity_channel);
|
||||||
|
Toolbar toolbar = (Toolbar) findViewById(R.id.toolbar);
|
||||||
|
setSupportActionBar(toolbar);
|
||||||
|
|
||||||
|
FloatingActionButton fab = (FloatingActionButton) findViewById(R.id.fab);
|
||||||
|
fab.setOnClickListener(new View.OnClickListener() {
|
||||||
|
@Override
|
||||||
|
public void onClick(View view) {
|
||||||
|
Snackbar.make(view, "Replace with your own action", Snackbar.LENGTH_LONG)
|
||||||
|
.setAction("Action", null).show();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
@ -85,7 +85,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,
|
||||||
|
@ -50,6 +50,7 @@ import java.util.Vector;
|
|||||||
|
|
||||||
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.ChannelExtractor;
|
||||||
import org.schabi.newpipe.extractor.MediaFormat;
|
import org.schabi.newpipe.extractor.MediaFormat;
|
||||||
import org.schabi.newpipe.extractor.ParsingException;
|
import org.schabi.newpipe.extractor.ParsingException;
|
||||||
import org.schabi.newpipe.extractor.ServiceList;
|
import org.schabi.newpipe.extractor.ServiceList;
|
||||||
@ -306,6 +307,7 @@ public class VideoItemDetailFragment extends Fragment {
|
|||||||
activity.findViewById(R.id.detailVideoThumbnailWindowBackgroundButton);
|
activity.findViewById(R.id.detailVideoThumbnailWindowBackgroundButton);
|
||||||
View topView = activity.findViewById(R.id.detailTopView);
|
View topView = activity.findViewById(R.id.detailTopView);
|
||||||
View nextVideoView = null;
|
View nextVideoView = null;
|
||||||
|
Button channelButton = (Button) activity.findViewById(R.id.channelButton);
|
||||||
if(info.next_video != null) {
|
if(info.next_video != null) {
|
||||||
nextVideoView = videoItemViewCreator
|
nextVideoView = videoItemViewCreator
|
||||||
.getViewFromVideoInfoItem(null, nextVideoFrame, info.next_video);
|
.getViewFromVideoInfoItem(null, nextVideoFrame, info.next_video);
|
||||||
@ -447,6 +449,14 @@ public class VideoItemDetailFragment extends Fragment {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
channelButton.setOnClickListener(new View.OnClickListener() {
|
||||||
|
@Override
|
||||||
|
public void onClick(View view) {
|
||||||
|
Intent i = new Intent(activity, ChannelActivity.class);
|
||||||
|
startActivity(i);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
} 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();
|
||||||
|
@ -0,0 +1,46 @@
|
|||||||
|
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, 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 abstract String getChannelName() throws ParsingException;
|
||||||
|
public abstract String getAvatarUrl() throws ParsingException;
|
||||||
|
public abstract String getBannerUrl() throws ParsingException;
|
||||||
|
}
|
@ -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);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -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;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -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();
|
||||||
|
|
||||||
|
@ -28,10 +28,10 @@ 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<Exception> 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;
|
||||||
}
|
}
|
||||||
@ -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, 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,75 @@
|
|||||||
|
package org.schabi.newpipe.extractor.services.youtube;
|
||||||
|
|
||||||
|
import android.util.Log;
|
||||||
|
|
||||||
|
import org.jsoup.Jsoup;
|
||||||
|
import org.jsoup.nodes.Document;
|
||||||
|
import org.schabi.newpipe.extractor.ChannelExtractor;
|
||||||
|
import org.schabi.newpipe.extractor.Downloader;
|
||||||
|
import org.schabi.newpipe.extractor.ExtractionException;
|
||||||
|
import org.schabi.newpipe.extractor.ParsingException;
|
||||||
|
import org.schabi.newpipe.extractor.UrlIdHandler;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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 Downloader downloader;
|
||||||
|
private final Document doc;
|
||||||
|
private final String siteUrl;
|
||||||
|
|
||||||
|
|
||||||
|
public YoutubeChannelExtractor(UrlIdHandler urlIdHandler, String url, Downloader dl, int serviceId)
|
||||||
|
throws ExtractionException, IOException {
|
||||||
|
super(urlIdHandler, url, dl, serviceId);
|
||||||
|
|
||||||
|
siteUrl = url;
|
||||||
|
downloader = dl;
|
||||||
|
String pageContent = downloader.download(url);
|
||||||
|
doc = Jsoup.parse(pageContent, url);
|
||||||
|
|
||||||
|
Log.d(TAG, pageContent);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getChannelName() throws ParsingException {
|
||||||
|
return getUrlIdHandler().getId(siteUrl);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getAvatarUrl() throws ParsingException {
|
||||||
|
try {
|
||||||
|
return doc.select("img[class=\"channel-header-profile-image\"]")
|
||||||
|
.first().attr("abs:src");
|
||||||
|
} catch(Exception e) {
|
||||||
|
throw new ParsingException("Could not get avatar", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getBannerUrl() throws ParsingException {
|
||||||
|
return "https://yt3.ggpht.com/-oF0YbeAGkaA/VBgrKvEGY1I/AAAAAAAACdw/nx02iZSseFw/w2120-fcrop64=1,00005a57ffffa5a8-nd-c0xffffffff-rj-k-no/Channel-Art-Template-%2528Photoshop%2529.png";
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,50 @@
|
|||||||
|
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/user/" + channelId + "/videos";
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getId(String siteUrl) throws ParsingException {
|
||||||
|
try {
|
||||||
|
return Parser.matchGroup1("/user/(.*)", siteUrl);
|
||||||
|
} catch(Exception e) {
|
||||||
|
throw new ParsingException("Could not get channel/user id", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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/");
|
||||||
|
}
|
||||||
|
}
|
@ -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);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -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, Downloader downloader)
|
||||||
|
throws ExtractionException, IOException {
|
||||||
|
return new YoutubeChannelExtractor(getChannelUrlIdHandlerInstance(), url, 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()
|
||||||
|
@ -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
|
||||||
|
@ -179,13 +179,18 @@
|
|||||||
android:text="100" />
|
android:text="100" />
|
||||||
</LinearLayout>
|
</LinearLayout>
|
||||||
|
|
||||||
|
<FrameLayout
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:id="@+id/detailUploaderFrame"
|
||||||
|
android:layout_below="@+id/linearLayout">
|
||||||
<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/detailUploaderLayout"
|
||||||
android:id="@+id/detailUploaderWrapView"
|
|
||||||
android:layout_marginTop="12dp">
|
android:layout_marginTop="12dp">
|
||||||
|
|
||||||
|
|
||||||
<View
|
<View
|
||||||
android:background="#000"
|
android:background="#000"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
@ -223,11 +228,21 @@
|
|||||||
android:layout_below="@id/detailUploaderThumbnailView"/>
|
android:layout_below="@id/detailUploaderThumbnailView"/>
|
||||||
</RelativeLayout>
|
</RelativeLayout>
|
||||||
|
|
||||||
|
<Button
|
||||||
|
android:layout_marginTop="11dp"
|
||||||
|
android:id="@+id/channelButton"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent"
|
||||||
|
android:background="?attr/selectableItemBackground"/>
|
||||||
|
</FrameLayout>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
<RelativeLayout android:id="@+id/detailNextVideoRootLayout"
|
<RelativeLayout android:id="@+id/detailNextVideoRootLayout"
|
||||||
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/detailUploaderFrame"
|
||||||
android:layout_centerHorizontal="true"
|
android:layout_centerHorizontal="true"
|
||||||
android:layout_marginTop="10dp">
|
android:layout_marginTop="10dp">
|
||||||
|
|
||||||
|
46
app/src/main/res/layout/activity_channel.xml
Normal file
46
app/src/main/res/layout/activity_channel.xml
Normal file
@ -0,0 +1,46 @@
|
|||||||
|
<?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"
|
||||||
|
tools:context="org.schabi.newpipe.ChannelActivity">
|
||||||
|
|
||||||
|
<android.support.design.widget.AppBarLayout
|
||||||
|
android:id="@+id/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/toolbar_layout"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent"
|
||||||
|
android:fitsSystemWindows="true"
|
||||||
|
app:contentScrim="?attr/colorPrimary"
|
||||||
|
app:layout_scrollFlags="scroll|exitUntilCollapsed">
|
||||||
|
|
||||||
|
<android.support.v7.widget.Toolbar
|
||||||
|
android:id="@+id/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>
|
||||||
|
|
||||||
|
<include layout="@layout/content_channel" />
|
||||||
|
|
||||||
|
<android.support.design.widget.FloatingActionButton
|
||||||
|
android:id="@+id/fab"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_margin="@dimen/fab_margin"
|
||||||
|
android:src="@android:drawable/ic_dialog_email"
|
||||||
|
app:layout_anchor="@id/app_bar"
|
||||||
|
app:layout_anchorGravity="bottom|end" />
|
||||||
|
|
||||||
|
</android.support.design.widget.CoordinatorLayout>
|
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>
|
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>
|
@ -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>
|
@ -37,5 +37,8 @@
|
|||||||
<!-- 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>
|
||||||
|
@ -176,6 +176,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>
|
||||||
@ -69,4 +68,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>
|
||||||
|
Loading…
Reference in New Issue
Block a user