1
0
mirror of https://github.com/TeamNewPipe/NewPipe synced 2026-01-14 02:32:40 +00:00

Compare commits

..

115 Commits

Author SHA1 Message Date
Adam Howard
c4ea73ca7a Merge pull request #93 from chschtsch/patch-6
remove quotation marks added by weblate
2015-11-13 12:19:21 +00:00
Adam Howard
2aa28d6453 Merge pull request #92 from chschtsch/patch-5
remove quotation marks added by weblate
2015-11-13 12:19:11 +00:00
Adam Howard
2d51085ccf Merge pull request #91 from chschtsch/patch-4
remove quotation marks added by weblate
2015-11-13 12:19:03 +00:00
Adam Howard
4eae02e47e Merge pull request #90 from chschtsch/patch-2
remove quotation marks added by weblate
2015-11-13 12:18:48 +00:00
Adam Howard
f3bf9f9e5d Merge pull request #89 from chschtsch/patch-3
remove quotation marks added by weblate
2015-11-13 12:18:38 +00:00
Greg
75d0b84bdb remove quotation marks added by weblate 2015-11-13 15:00:24 +03:00
Greg
3643ddcf5c remove quotation marks added by weblate 2015-11-13 14:58:16 +03:00
Greg
35500e8ef7 remove quotation marks added by weblate 2015-11-13 14:57:52 +03:00
Greg
6badcf5391 remove quotation marks added by weblate 2015-11-13 14:57:09 +03:00
Greg
d4898043f6 remove quotation marks added by weblate 2015-11-13 14:56:38 +03:00
Weblate
2322ba833a Merge remote-tracking branch 'origin/master' 2015-11-13 12:27:21 +01:00
Greg
5aabb2917f Translated using Weblate (Spanish)
Currently translated at 95.6% (44 of 46 strings)
2015-11-13 12:27:21 +01:00
Greg
eacbf6ce50 Translated using Weblate (German)
Currently translated at 97.8% (45 of 46 strings)
2015-11-13 12:27:20 +01:00
Greg
031300a132 Translated using Weblate (French)
Currently translated at 76.0% (35 of 46 strings)
2015-11-13 12:27:20 +01:00
Greg
f905611e69 Translated using Weblate (English)
Currently translated at 100.0% (46 of 46 strings)

changing 'published' to 'updloaded'
2015-11-13 12:27:20 +01:00
Greg
e475f9f876 Translated using Weblate (Dutch)
Currently translated at 97.8% (45 of 46 strings)
2015-11-13 12:27:20 +01:00
Adam Howard
b65263349e Merged upstream translation commits 2015-11-13 11:07:22 +00:00
Adam Howard
5cb8026f6d Merge pull request #87 from chschtsch/patch-1
remove quotation marks added by weblate
2015-11-13 11:05:48 +00:00
Adam Howard
cc7ce5cf93 fixed inefficient double-conversion of parceled VideoInfoItems, from being cast from VideoInfoItem[] to Vector<>, to using ArrayList as an implementation of List 2015-11-13 10:47:05 +00:00
Greg
af506639a9 remove quotation marks added by weblate 2015-11-13 12:46:55 +03:00
Greg
d377d67174 Translated using Weblate (Russian)
Currently translated at 100.0% (46 of 46 strings)
2015-11-13 10:06:06 +01:00
Greg
cf926353d1 Translated using Weblate (Russian)
Currently translated at 100.0% (46 of 46 strings)
2015-11-13 09:54:54 +01:00
Weblate
d686c744d0 Merge remote-tracking branch 'origin/master' 2015-11-13 03:22:52 +01:00
Greg
501c60b180 Translated using Weblate (Russian)
Currently translated at 97.8% (45 of 46 strings)
2015-11-13 03:22:52 +01:00
jasperweiss
da36687e25 Translated using Weblate (Dutch)
Currently translated at 100.0% (46 of 46 strings)
2015-11-13 03:22:52 +01:00
Adam Howard
f13f9a066a updated README to include language-specific search feature 2015-11-12 15:51:22 +00:00
Christian Schabesberger
8eaa4f7654 Merge pull request #82 from chschtsch/master
fix bug with parsing like/dislike count
2015-11-12 10:26:50 +01:00
Greg
145a7f8e0d fix bug with parsing like/dislike count 2015-11-12 12:18:41 +03:00
Adam Howard
67ba126602 implemented locale-specific formatting of view, like and dislike counts, and video published date 2015-11-11 16:23:22 +00:00
Adam Howard
c5084901b5 removed commented-out code, added another TODO note 2015-11-11 13:08:53 +00:00
Adam Howard
3bfc82f7c0 Refactoring YoutubeExtractor:
-replaced single use of terrible_unescape_workaround_fuck(String) with call to URLDecoder.decode(String, String)
* tested new regex implementation of YoutubeExtractor.getVideoId(String)
- deleted old HashMap-based implementation of YoutubeExtractor.getVideoId(String)
* Miscellaneous typo corrections
* replaced direct page-scraping extraction of video publication date in YoutubeExtractor.getVideoInfo(String) with jsoup-based scrape of <meta> tag field in YYYY-MM-DD format
*similarly,  replaced direct page-scraping extraction of view count with <meta> tag field.

Both <meta> tag fields still need to be formatted locale-specifically
2015-11-11 13:07:09 +00:00
Adam Howard
3411b53450 implemented optimised version of YoutubeExtractor.getVideoId().
new version uses a regular expression instead of creating a HashMap and looping over them.
Needs testing before pushing to origin
2015-11-11 01:48:44 +00:00
Christian Schabesberger
873564f2aa removed .idea folder 2015-11-10 21:17:07 +01:00
Christian Schabesberger
353ed90d12 New version number 0.6.0 and some fixes:
* moved on to version 0.6.0
* fixed youtube url sicnature encryption (vevo videos can be watched again)
* removed play action from the ActionBar
* rolled back changes for hiding the InfoBar inside PlayVideoActivity
* some small layout changes
* removed some files to be ignored (mostly inside .idea directory)
* etc
2015-11-10 20:51:39 +01:00
Christian Schabesberger
799faecc5b Merge pull request #80 from chschtsch/master
Update layout (initially suggested by @darkon5) + miscellaneous fixes
2015-11-10 19:28:53 +01:00
chschtsch
731321b1f9 tryna merge dem files 2015-11-10 20:50:15 +03:00
chschtsch
7ff43caf96 tryna merge dem files 2015-11-10 20:36:35 +03:00
chschtsch
d0877c3132 tryna merge dem files 2015-11-10 20:34:08 +03:00
chschtsch
c589b03dcb rollback setting English as a default language for content 2015-11-10 20:28:05 +03:00
chschtsch
862b5aaef6 update layout (implementing redesign suggested by @darkon5) & set english as a default language for content 2015-11-10 20:26:20 +03:00
Adam Howard
596443bf5e added licensing notice to MediaFormat 2015-11-10 17:23:07 +00:00
Adam Howard
27b450f1e3 minor commit:
-removed now-redundant language code check
2015-11-10 17:09:26 +00:00
chschtsch
61a09e97ca update colors (implementing redesign suggested by @darkon5) 2015-11-10 19:55:58 +03:00
chschtsch
224e7a8969 update date extracting regex pattern + fix some strings in English 2015-11-10 19:50:04 +03:00
Adam Howard
9e7d9ee973 merged commits from origin 2015-11-10 12:43:40 +00:00
Weblate
586bad345c Merge remote-tracking branch 'origin/master' 2015-11-09 08:16:47 +01:00
Szöllősi Attila
abdd7dc7d3 Translated using Weblate (Hungarian)
Currently translated at 97.8% (45 of 46 strings)
2015-11-09 08:16:47 +01:00
jasperweiss
aee32f7a3e Translated using Weblate (Dutch)
Currently translated at 100.0% (46 of 46 strings)
2015-11-09 08:16:45 +01:00
Christian Schabesberger
696760e65a removed printing the player url 2015-11-08 21:22:24 +01:00
Christian Schabesberger
200db15d4b Merge branch 'master' of github.com:theScrabi/NewPipe 2015-11-08 21:19:09 +01:00
Christian Schabesberger
33e332f105 impofed performance and made similar video button hidable 2015-11-08 21:17:51 +01:00
Timofonic
bb2955e442 Translated using Weblate (Spanish)
Currently translated at 100.0% (46 of 46 strings)
2015-11-08 19:54:28 +01:00
Adam Howard
2fc2fa56c3 refactoring VideoInfo & MediaFormat, part 2:
* fixed errors caused by moving media format code to MediaFormat enum
2015-11-08 02:22:40 +00:00
Adam Howard
c87458590c refactoring VideoInfo & VideoInfoItem, part 1:
+ created enum VideoFormat
2015-11-08 02:04:03 +00:00
Adam Howard
8aff134c56 replaced content tountry selection with search language selection.Should now allows users to search for videos in their preferred language. Needs testing before pushing to origin. 2015-11-08 01:09:03 +00:00
Christian Schabesberger
4a938b81df Merge pull request #73 from joshsoftware/feature_search_suggestion
Feature search suggestion (enhancement)
2015-11-05 12:36:43 +01:00
chandrashekhar
4def715b25 + Added TODO & remove unused lines. 2015-11-05 13:19:28 +05:30
chandrashekhar
821acf12d8 +Merge Adam Howard's (medavox) commit with my changes & remove conflict.
+update searchEngine.java & YoutubeSearchEngine.java file.

Conflicts:
	app/src/main/java/org/schabi/newpipe/SearchEngine.java
	app/src/main/java/org/schabi/newpipe/youtube/YoutubeSearchEngine.java
2015-11-05 13:03:47 +05:30
Adam Howard
fc707b6c7e Added Content Country selection setting. This changes the SearchEngine interface slightly. Still need a reliable test to prove whether YouTube is serving different search results based on the country code passed in the search query. 2015-11-04 12:42:17 +00:00
chandrashekhar
85ac000479 + Change return type of interface method suggestionList, So that it will return arraylist of string type data(suggestion). 2015-11-04 11:44:34 +05:30
chandrashekhar
68a0eefa20 + Added interface method in SearchEngine class for fetch search suggestion list.
+ Implemented SearchEngine Interface method in YoutubeSearchEngine class.
2015-11-04 09:06:21 +05:30
Adam Howard
fc32377ce7 added check and exception reporting to YoutubeExtractor:matchGroup1, so that a failed match doesn't go unnoticed 2015-11-04 00:29:30 +00:00
Christian Schabesberger
59e512a64d fixed notification hiding issue 2015-11-03 22:19:28 +01:00
Christian Schabesberger
c51a5a51f1 Merge pull request #72 from medavox/master
Code cleanup & minor additions
2015-11-03 17:54:09 +01:00
Adam Howard
9546a276dc modified gitignore so jdk versions changes are hopefully not propagated to remote 2015-11-03 13:20:15 +00:00
Adam Howard
a18353df5f still fighting with jdk version strings in app/app.iml, .idea/gradle.xml, .idea/misc.xml 2015-11-03 13:19:23 +00:00
Adam Howard
3c72113f4c replaced 4-line regex blocks with call to equivalent method; removed unecessary try-catch blocks surrounding them: regex methods don't throw runtime exceptions 2015-11-03 12:56:11 +00:00
Adam Howard
7e193751c4 fixed app/app.iml, .idea/grade.xml and .idea/misc.xml siwtching jdk versions between 1.7 and 1.8. Added them to my local .gitignore 2015-11-03 12:08:30 +00:00
Adam Howard
56c96eb712 Merge remote-tracking branch 'upstream/master':
Synced new russian translation from theScrabi/NewPipe
2015-11-03 11:51:40 +00:00
Adam Howard
4106a984ca minor corrections pre-sync 2015-11-03 11:51:18 +00:00
Christian Schabesberger
10f1ab0598 Merge pull request #71 from chschtsch/patch-1
Add russian translation of strings.xml
2015-11-03 11:28:27 +01:00
chschtsch
bca9603440 Add russian translation of strings.xml 2015-11-03 12:34:51 +03:00
Adam Howard
c32c267889 Merge remote-tracking branch 'upstream/master'
Conflicts:
	app/src/main/java/org/schabi/newpipe/VideoItemDetailFragment.java -- still used class-based passing
	app/src/main/java/org/schabi/newpipe/VideoItemListActivity.java -- just some whitespace
2015-11-02 21:29:43 +00:00
Adam Howard
627e987bda made the PlayVideoActivity return to its VideoItemDetailActivity when the back button is pressed, by setting its parent activity in the manifest. Also grammar fixes for existing comments 2015-11-02 21:19:18 +00:00
Christian Schabesberger
7c18e147f3 added "show next/related videos" to features 2015-11-02 20:32:13 +01:00
Christian Schabesberger
6a8fb5910d changed icon in the README file 2015-11-02 20:18:23 +01:00
Christian Schabesberger
b865326d51 added ability to show similar videos 2015-11-02 19:57:47 +01:00
Adam Howard
a2d5b0893d Merge remote-tracking branch 'upstream/master' 2015-11-02 15:12:34 +00:00
Adam Howard
db0508b9ab refactored StreamingService interface so it acts as a Factory (returning new instances of called classes, eg Extractor), rather than passing Class objects (which loses type safety) which are then instantiated. Also noticed there is a conflict between existing gradle setup and mine: misc.xml and app.iml have had their jvm version values switched from 1.8 to 1.7 2015-11-02 15:03:11 +00:00
Christian Schabesberger
1850dee93a Merge pull request #67 from joshsoftware/remove_deprecation
Remove Deprecated methods
2015-11-02 15:37:17 +01:00
Weblate
7b1eb8a6dc Merge remote-tracking branch 'origin/master' 2015-11-02 14:26:43 +01:00
chandrashekhar
95a9f2f5e3 + Remove deprecated method from app/src/main/java/org/schabi/newpipe/VideoItemListFragment.java
onAttach(Activity activity) is replaced by onAttach(Context context)

+ Remove deprecated method from pp/src/main/java/org/schabi/newpipe/VideoListAdapter.java
context.getResource().getColor(resId) is replaced by ContextCompat.getColor(context,resId)
2015-11-02 18:02:32 +05:30
darkon5
ae7ed2d226 Merge pull request #65 from medavox/master
Minor README edits
2015-11-02 10:18:24 +01:00
Szöllősi Attila
20cf82bab1 Translated using Weblate (Hungarian)
Currently translated at 100.0% (44 of 44 strings)
2015-11-01 21:42:52 +01:00
Mladen Pejaković
5dcb1e26b5 Translated using Weblate (Serbian)
Currently translated at 100.0% (44 of 44 strings)
2015-10-31 19:56:57 +01:00
Christian Schabesberger
8076589180 Translated using Weblate (German)
Currently translated at 100.0% (44 of 44 strings)
2015-10-31 16:11:54 +01:00
Christian Schabesberger
edbd4003be Merge pull request #64 from mamins1376/master
add Persian translation
2015-10-31 13:53:26 +01:00
medavox
122b089bf0 minor grammar 2015-10-31 12:07:07 +00:00
medavox
a28d917990 fixed typo 2015-10-31 12:05:42 +00:00
Mohammad Amin Sameti
5b605a1100 add Persian translation 2015-10-31 02:30:08 +03:30
Christian Schabesberger
f67158a2a7 Fixed:
- made "could not find a streamingplayer" thing inside ActionBarHandler yield stacktraces
- remove watermark
- fixed fab layout
- changed version number to 0.5.0
2015-10-29 18:37:32 +01:00
Christian Schabesberger
c22c2009d4 - changed icon again
- made ActionBarHandler not be a singelton anymore
 - fixed go back bug for the "Next Video" thing
 - fixed opening youtube mobile links
2015-10-29 17:56:35 +01:00
Christian Schabesberger
ab4d626ea9 fixed opening videos via firefox/fenec 2015-10-28 20:48:03 +01:00
Christian Schabesberger
96709d22e9 Merge pull request #61 from Natureshadow/patch-1
Add missing patterns as described in isue #50
2015-10-28 19:39:06 +01:00
Christian Schabesberger
f0bd171eee small layout changed in README.md 2015-10-28 19:08:58 +01:00
Dominik George
c4191077f3 Add missing patterns as described in isue #50 2015-10-28 15:24:46 +01:00
Christian Schabesberger
321d090052 - added dracon5's website to README.md
- added halcyonest icon (with smal modifications)
2015-10-27 21:35:08 +01:00
Christian Schabesberger
32dcb4d281 improved README file 2015-10-27 20:01:56 +01:00
Christian Schabesberger
080159849e - added preference for "next video" item
- display if a url is supported or nod
2015-10-27 18:13:04 +01:00
Christian Schabesberger
d9e690f62c added NextVideo support 2015-10-25 19:14:29 +01:00
Christian Schabesberger
8c0156dea3 fixed broken commit 2015-10-24 16:05:23 +02:00
Christian Schabesberger
038c59ce66 fiexed some smaller displaying errors 2015-10-24 16:04:27 +02:00
Christian Schabesberger
72e08c0447 fixed some layout bugs 2015-10-23 23:08:01 +02:00
Christian Schabesberger
173eaa8cf8 Merge pull request #51 from Soofe/master
Fixed some typos and removed unused imports.
2015-10-17 17:22:21 +02:00
Christian Schabesberger
a04cd24e5e Merge pull request #53 from pejakm/srupd
Update Serbian translation
2015-10-17 17:15:53 +02:00
Mladen Pejaković
0e11404b3b Update Serbian translation 2015-10-17 17:13:21 +02:00
Soofe
342807e26a Update VideoItemListFragment.java 2015-10-15 23:58:14 +02:00
Soofe
c068f08ff8 Fixed some typos and removed unused imports. 2015-10-15 23:25:53 +02:00
Christian Schabesberger
b7c0a77edc Merge pull request #45 from ata2001/master
Update hungarian translation
2015-09-25 23:33:01 +02:00
ata2001
aab0f45890 Update hungarian translation 2015-09-26 00:05:54 +02:00
Christian Schabesberger
c62ad66f11 New:
- Show video title instead of stream url in vlc/MXPlayer
 - remember rotation
Fix:
 - sensore controlled landscape rotation in the player
2015-09-25 14:17:43 +02:00
Christian Schabesberger
d8bdada9db Merge pull request #43 from epitron/master
Set the title for external video players
2015-09-25 07:43:46 +02:00
Christian Schabesberger
1b9f7a7654 Merge pull request #40 from ata2001/master
Update hungarian translation
2015-09-25 07:42:41 +02:00
Chris Gahan
ac710fff08 Set the title for external video players. 2015-09-23 21:58:41 -04:00
ata2001
2e09492eef Update hungarian translation 2015-09-23 19:01:09 +02:00
64 changed files with 2835 additions and 1041 deletions

5
.gitignore vendored
View File

@@ -1,7 +1,8 @@
.gitignore
.gradle
/local.properties
/.idea/workspace.xml
/.idea/libraries
.DS_Store
/build
/captures
/app/app.iml
/.idea

1
.idea/.name generated
View File

@@ -1 +0,0 @@
NewPipe

22
.idea/compiler.xml generated
View File

@@ -1,22 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="CompilerConfiguration">
<resourceExtensions />
<wildcardResourcePatterns>
<entry name="!?*.java" />
<entry name="!?*.form" />
<entry name="!?*.class" />
<entry name="!?*.groovy" />
<entry name="!?*.scala" />
<entry name="!?*.flex" />
<entry name="!?*.kt" />
<entry name="!?*.clj" />
<entry name="!?*.aj" />
</wildcardResourcePatterns>
<annotationProcessing>
<profile default="true" name="Default" enabled="false">
<processorPath useClasspath="true" />
</profile>
</annotationProcessing>
</component>
</project>

View File

@@ -1,3 +0,0 @@
<component name="CopyrightManager">
<settings default="" />
</component>

View File

@@ -1,3 +0,0 @@
<component name="ProjectDictionaryState">
<dictionary name="the-scrabi" />
</component>

19
.idea/gradle.xml generated
View File

@@ -1,19 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="GradleSettings">
<option name="linkedExternalProjectsSettings">
<GradleProjectSettings>
<option name="distributionType" value="LOCAL" />
<option name="externalProjectPath" value="$PROJECT_DIR$" />
<option name="gradleHome" value="$APPLICATION_HOME_DIR$/gradle/gradle-2.4" />
<option name="gradleJvm" value="1.8" />
<option name="modules">
<set>
<option value="$PROJECT_DIR$" />
<option value="$PROJECT_DIR$/app" />
</set>
</option>
</GradleProjectSettings>
</option>
</component>
</project>

46
.idea/misc.xml generated
View File

@@ -1,46 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="EntryPointsManager">
<entry_points version="2.0" />
</component>
<component name="NullableNotNullManager">
<option name="myDefaultNullable" value="android.support.annotation.Nullable" />
<option name="myDefaultNotNull" value="android.support.annotation.NonNull" />
<option name="myNullables">
<value>
<list size="4">
<item index="0" class="java.lang.String" itemvalue="org.jetbrains.annotations.Nullable" />
<item index="1" class="java.lang.String" itemvalue="javax.annotation.Nullable" />
<item index="2" class="java.lang.String" itemvalue="edu.umd.cs.findbugs.annotations.Nullable" />
<item index="3" class="java.lang.String" itemvalue="android.support.annotation.Nullable" />
</list>
</value>
</option>
<option name="myNotNulls">
<value>
<list size="4">
<item index="0" class="java.lang.String" itemvalue="org.jetbrains.annotations.NotNull" />
<item index="1" class="java.lang.String" itemvalue="javax.annotation.Nonnull" />
<item index="2" class="java.lang.String" itemvalue="edu.umd.cs.findbugs.annotations.NonNull" />
<item index="3" class="java.lang.String" itemvalue="android.support.annotation.NonNull" />
</list>
</value>
</option>
</component>
<component name="ProjectLevelVcsManager" settingsEditedManually="false">
<OptionsSetting value="true" id="Add" />
<OptionsSetting value="true" id="Remove" />
<OptionsSetting value="true" id="Checkout" />
<OptionsSetting value="true" id="Update" />
<OptionsSetting value="true" id="Status" />
<OptionsSetting value="true" id="Edit" />
<ConfirmationsSetting value="0" id="Add" />
<ConfirmationsSetting value="0" id="Remove" />
</component>
<component name="ProjectRootManager" version="2" languageLevel="JDK_1_7" default="true" assert-keyword="true" jdk-15="true" project-jdk-name="1.8" project-jdk-type="JavaSDK">
<output url="file://$PROJECT_DIR$/build/classes" />
</component>
<component name="ProjectType">
<option name="id" value="Android" />
</component>
</project>

9
.idea/modules.xml generated
View File

@@ -1,9 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="ProjectModuleManager">
<modules>
<module fileurl="file://$PROJECT_DIR$/NewPipe.iml" filepath="$PROJECT_DIR$/NewPipe.iml" />
<module fileurl="file://$PROJECT_DIR$/app/app.iml" filepath="$PROJECT_DIR$/app/app.iml" />
</modules>
</component>
</project>

View File

@@ -1,12 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="RunConfigurationProducerService">
<option name="ignoredProducers">
<set>
<option value="org.jetbrains.plugins.gradle.execution.test.runner.AllInPackageGradleConfigurationProducer" />
<option value="org.jetbrains.plugins.gradle.execution.test.runner.TestClassGradleConfigurationProducer" />
<option value="org.jetbrains.plugins.gradle.execution.test.runner.TestMethodGradleConfigurationProducer" />
</set>
</option>
</component>
</project>

6
.idea/vcs.xml generated
View File

@@ -1,6 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="VcsDirectoryMappings">
<mapping directory="$PROJECT_DIR$" vcs="Git" />
</component>
</project>

View File

@@ -1,19 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<module external.linked.project.id="NewPipe" external.linked.project.path="$MODULE_DIR$" external.root.project.path="$MODULE_DIR$" external.system.id="GRADLE" external.system.module.group="" external.system.module.version="unspecified" type="JAVA_MODULE" version="4">
<component name="FacetManager">
<facet type="java-gradle" name="Java-Gradle">
<configuration>
<option name="BUILD_FOLDER_PATH" value="$MODULE_DIR$/build" />
<option name="BUILDABLE" value="false" />
</configuration>
</facet>
</component>
<component name="NewModuleRootManager" LANGUAGE_LEVEL="JDK_1_7" inherit-compiler-output="true">
<exclude-output />
<content url="file://$MODULE_DIR$">
<excludeFolder url="file://$MODULE_DIR$/.gradle" />
</content>
<orderEntry type="inheritedJdk" />
<orderEntry type="sourceFolder" forTests="false" />
</component>
</module>

View File

@@ -1,7 +1,46 @@
NewPipe
-------
# NewPipe
[![Translation Status](https://hosted.weblate.org/widgets/NewPipe/-/svg-badge.svg)](https://hosted.weblate.org/engage/NewPipe/)
NewPipe is a lightweight youtube frontend for android. It's supposed to be used without the youtube-api and without any google play services. NewPipe only parses the youtube website in order to gain the information it needs.
[![NewPipe](https://f-droid.org/repo/icons/org.schabi.newpipe.5.png)](http://dasochan.nl/newpipe/)
NewPipe: A free lightweight Youtube frontend for Android.
This a very early version of the app, so not all functionality is implemented, and there may still be a lot of bugs. But all in all it's doing what it is supposed to do. It makes it possible to watch youtube videos. So don't be cruel to this app. It will improve...
[![F-Droid](https://f-droid.org/wiki/images/0/06/F-Droid-button_get-it-on.png)](https://f-droid.org/repository/browse/?fdfilter=newpipe&fdid=org.schabi.newpipe)
## Description
NewPipe does not use any Google framework libraries, or the YouTube API. It only parses the website in order to gain the information it needs. Therefore this app can be used on devices without G-services installed. Also NewPipe does not store data on the YouTube website (no login), and it's free software.
## Features
* Search videos
* Display general information about a video
* Watch Youtube videos
* Listen to Youtube videos (audio only streaming)
* Select the streaming player to watch the video with
* Download videos (working, but it could be better)
* Download audio only (working but, but it could be better)
* Open a video in Kodi
* Show Next/Related videos
* Search Youtube in a specific language
## Coming Features
* Improved Downloading
* Bookmarks
* View history
* Search history
* Search channels
* Display general information about channels
* Subscribe to channels
* Watch videos from a channel
* Search/Watch Playlists
* ... and many more
### Multi service support
Generally NewPipe is designed to not only support YouTube, but many more streaming services. However, right now NewPipe is not stable enough to support more than only YouTube. But if all works as planned, NewPipe will get such support by the version 2.0.
# Help is always welcome !!!
Whether it's about ideas, translation, design changes, code cleaning, or real heavy code changes. Help is always welcome.
The more is done the better it gets!

2
app/.gitignore vendored
View File

@@ -1 +1,3 @@
.gitignore
/build
app.iml

View File

@@ -1,101 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<module external.linked.project.id=":app" external.linked.project.path="$MODULE_DIR$" external.root.project.path="$MODULE_DIR$/.." external.system.id="GRADLE" external.system.module.group="NewPipe" external.system.module.version="unspecified" type="JAVA_MODULE" version="4">
<component name="FacetManager">
<facet type="android-gradle" name="Android-Gradle">
<configuration>
<option name="GRADLE_PROJECT_PATH" value=":app" />
</configuration>
</facet>
<facet type="android" name="Android">
<configuration>
<option name="SELECTED_BUILD_VARIANT" value="debug" />
<option name="SELECTED_TEST_ARTIFACT" value="_android_test_" />
<option name="ASSEMBLE_TASK_NAME" value="assembleDebug" />
<option name="COMPILE_JAVA_TASK_NAME" value="compileDebugSources" />
<option name="ASSEMBLE_TEST_TASK_NAME" value="assembleDebugAndroidTest" />
<option name="COMPILE_JAVA_TEST_TASK_NAME" value="compileDebugAndroidTestSources" />
<afterSyncTasks>
<task>generateDebugAndroidTestSources</task>
<task>generateDebugSources</task>
</afterSyncTasks>
<option name="ALLOW_USER_CONFIGURATION" value="false" />
<option name="MANIFEST_FILE_RELATIVE_PATH" value="/src/main/AndroidManifest.xml" />
<option name="RES_FOLDER_RELATIVE_PATH" value="/src/main/res" />
<option name="RES_FOLDERS_RELATIVE_PATH" value="file://$MODULE_DIR$/src/main/res" />
<option name="ASSETS_FOLDER_RELATIVE_PATH" value="/src/main/assets" />
</configuration>
</facet>
</component>
<component name="NewModuleRootManager" LANGUAGE_LEVEL="JDK_1_7" inherit-compiler-output="false">
<output url="file://$MODULE_DIR$/build/intermediates/classes/debug" />
<output-test url="file://$MODULE_DIR$/build/intermediates/classes/androidTest/debug" />
<exclude-output />
<content url="file://$MODULE_DIR$">
<sourceFolder url="file://$MODULE_DIR$/build/generated/source/r/debug" isTestSource="false" generated="true" />
<sourceFolder url="file://$MODULE_DIR$/build/generated/source/aidl/debug" isTestSource="false" generated="true" />
<sourceFolder url="file://$MODULE_DIR$/build/generated/source/buildConfig/debug" isTestSource="false" generated="true" />
<sourceFolder url="file://$MODULE_DIR$/build/generated/source/rs/debug" isTestSource="false" generated="true" />
<sourceFolder url="file://$MODULE_DIR$/build/generated/res/rs/debug" type="java-resource" />
<sourceFolder url="file://$MODULE_DIR$/build/generated/res/resValues/debug" type="java-resource" />
<sourceFolder url="file://$MODULE_DIR$/build/generated/source/r/androidTest/debug" isTestSource="true" generated="true" />
<sourceFolder url="file://$MODULE_DIR$/build/generated/source/aidl/androidTest/debug" isTestSource="true" generated="true" />
<sourceFolder url="file://$MODULE_DIR$/build/generated/source/buildConfig/androidTest/debug" isTestSource="true" generated="true" />
<sourceFolder url="file://$MODULE_DIR$/build/generated/source/rs/androidTest/debug" isTestSource="true" generated="true" />
<sourceFolder url="file://$MODULE_DIR$/build/generated/res/rs/androidTest/debug" type="java-test-resource" />
<sourceFolder url="file://$MODULE_DIR$/build/generated/res/resValues/androidTest/debug" type="java-test-resource" />
<sourceFolder url="file://$MODULE_DIR$/src/debug/res" type="java-resource" />
<sourceFolder url="file://$MODULE_DIR$/src/debug/resources" type="java-resource" />
<sourceFolder url="file://$MODULE_DIR$/src/debug/assets" type="java-resource" />
<sourceFolder url="file://$MODULE_DIR$/src/debug/aidl" isTestSource="false" />
<sourceFolder url="file://$MODULE_DIR$/src/debug/java" isTestSource="false" />
<sourceFolder url="file://$MODULE_DIR$/src/debug/jni" isTestSource="false" />
<sourceFolder url="file://$MODULE_DIR$/src/debug/rs" isTestSource="false" />
<sourceFolder url="file://$MODULE_DIR$/src/main/res" type="java-resource" />
<sourceFolder url="file://$MODULE_DIR$/src/main/resources" type="java-resource" />
<sourceFolder url="file://$MODULE_DIR$/src/main/assets" type="java-resource" />
<sourceFolder url="file://$MODULE_DIR$/src/main/aidl" isTestSource="false" />
<sourceFolder url="file://$MODULE_DIR$/src/main/java" isTestSource="false" />
<sourceFolder url="file://$MODULE_DIR$/src/main/jni" isTestSource="false" />
<sourceFolder url="file://$MODULE_DIR$/src/main/rs" isTestSource="false" />
<sourceFolder url="file://$MODULE_DIR$/src/androidTest/res" type="java-test-resource" />
<sourceFolder url="file://$MODULE_DIR$/src/androidTest/resources" type="java-test-resource" />
<sourceFolder url="file://$MODULE_DIR$/src/androidTest/assets" type="java-test-resource" />
<sourceFolder url="file://$MODULE_DIR$/src/androidTest/aidl" isTestSource="true" />
<sourceFolder url="file://$MODULE_DIR$/src/androidTest/java" isTestSource="true" />
<sourceFolder url="file://$MODULE_DIR$/src/androidTest/jni" isTestSource="true" />
<sourceFolder url="file://$MODULE_DIR$/src/androidTest/rs" isTestSource="true" />
<excludeFolder url="file://$MODULE_DIR$/build/intermediates/assets" />
<excludeFolder url="file://$MODULE_DIR$/build/intermediates/bundles" />
<excludeFolder url="file://$MODULE_DIR$/build/intermediates/classes" />
<excludeFolder url="file://$MODULE_DIR$/build/intermediates/coverage-instrumented-classes" />
<excludeFolder url="file://$MODULE_DIR$/build/intermediates/dependency-cache" />
<excludeFolder url="file://$MODULE_DIR$/build/intermediates/dex" />
<excludeFolder url="file://$MODULE_DIR$/build/intermediates/dex-cache" />
<excludeFolder url="file://$MODULE_DIR$/build/intermediates/exploded-aar/com.android.support/appcompat-v7/23.0.1/jars" />
<excludeFolder url="file://$MODULE_DIR$/build/intermediates/exploded-aar/com.android.support/design/23.0.1/jars" />
<excludeFolder url="file://$MODULE_DIR$/build/intermediates/exploded-aar/com.android.support/support-v4/23.0.1/jars" />
<excludeFolder url="file://$MODULE_DIR$/build/intermediates/incremental" />
<excludeFolder url="file://$MODULE_DIR$/build/intermediates/jacoco" />
<excludeFolder url="file://$MODULE_DIR$/build/intermediates/javaResources" />
<excludeFolder url="file://$MODULE_DIR$/build/intermediates/libs" />
<excludeFolder url="file://$MODULE_DIR$/build/intermediates/lint" />
<excludeFolder url="file://$MODULE_DIR$/build/intermediates/manifests" />
<excludeFolder url="file://$MODULE_DIR$/build/intermediates/ndk" />
<excludeFolder url="file://$MODULE_DIR$/build/intermediates/pre-dexed" />
<excludeFolder url="file://$MODULE_DIR$/build/intermediates/proguard" />
<excludeFolder url="file://$MODULE_DIR$/build/intermediates/res" />
<excludeFolder url="file://$MODULE_DIR$/build/intermediates/rs" />
<excludeFolder url="file://$MODULE_DIR$/build/intermediates/symbols" />
<excludeFolder url="file://$MODULE_DIR$/build/outputs" />
<excludeFolder url="file://$MODULE_DIR$/build/tmp" />
</content>
<orderEntry type="jdk" jdkName="Android API 23 Platform" jdkType="Android SDK" />
<orderEntry type="sourceFolder" forTests="false" />
<orderEntry type="library" exported="" name="jsoup-1.8.3" level="project" />
<orderEntry type="library" exported="" name="support-v4-23.0.1" level="project" />
<orderEntry type="library" exported="" name="rhino-1.7.7" level="project" />
<orderEntry type="library" exported="" name="design-23.0.1" level="project" />
<orderEntry type="library" exported="" name="appcompat-v7-23.0.1" level="project" />
<orderEntry type="library" exported="" name="support-annotations-23.0.1" level="project" />
</component>
</module>

View File

@@ -8,8 +8,8 @@ android {
applicationId "org.schabi.newpipe"
minSdkVersion 15
targetSdkVersion 23
versionCode 4
versionName "0.4.1"
versionCode 6
versionName "0.6.0"
}
buildTypes {
release {
@@ -21,9 +21,9 @@ android {
dependencies {
compile fileTree(include: ['*.jar'], dir: 'libs')
compile 'com.android.support:appcompat-v7:23.0.1'
compile 'com.android.support:support-v4:23.0.1'
compile 'com.android.support:appcompat-v7:23.1.0'
compile 'com.android.support:support-v4:23.1.0'
compile 'com.android.support:design:23.1.0'
compile 'org.jsoup:jsoup:1.8.3'
compile 'org.mozilla:rhino:1.7.7'
compile 'com.android.support:design:23.0.1'
}

View File

@@ -34,27 +34,27 @@
<data
android:host="youtube.com"
android:scheme="http"
android:pathPrefix="/watch"/>
android:pathPattern="/?*#*/*watch"/>
<data
android:host="youtube.com"
android:scheme="https"
android:pathPrefix="/watch"/>
android:pathPattern="/?*#*/*watch"/>
<data
android:host="www.youtube.com"
android:scheme="http"
android:pathPrefix="/watch"/>
android:pathPattern="/?*#*/*watch"/>
<data
android:host="www.youtube.com"
android:scheme="https"
android:pathPrefix="/watch"/>
android:pathPattern="/?*#*/*watch"/>
<data
android:host="m.youtube.com"
android:scheme="http"
android:pathPrefix="/watch"/>
android:pathPattern="/?*#*/*watch"/>
<data
android:host="m.youtube.com"
android:scheme="https"
android:pathPrefix="/watch"/>
android:pathPattern="/?*#*/*watch"/>
<data
android:host="youtu.be"
android:scheme="https"
@@ -68,6 +68,7 @@
<activity android:name=".PlayVideoActivity"
android:configChanges="orientation|keyboardHidden|screenSize"
android:theme="@style/FullscreenTheme"
android:parentActivityName=".VideoItemDetailActivity"
>
</activity>
<activity

View File

@@ -1,13 +1,10 @@
package org.schabi.newpipe;
import android.app.DownloadManager;
import android.content.Context;
import android.content.DialogInterface;
import android.content.Intent;
import android.content.SharedPreferences;
import android.net.Uri;
import android.os.Bundle;
import android.preference.Preference;
import android.preference.PreferenceManager;
import android.support.v4.view.MenuItemCompat;
import android.support.v7.app.ActionBar;
@@ -19,8 +16,6 @@ import android.view.MenuInflater;
import android.view.MenuItem;
import android.widget.ArrayAdapter;
import java.io.File;
/**
* Created by Christian Schabesberger on 18.08.15.
*
@@ -45,10 +40,7 @@ public class ActionBarHandler {
private static final String TAG = ActionBarHandler.class.toString();
private static final String KORE_PACKET = "org.xbmc.kore";
private static ActionBarHandler handler = null;
private Context context = null;
private String webisteUrl = "";
private String websiteUrl = "";
private AppCompatActivity activity;
private VideoInfo.VideoStream[] videoStreams = null;
private VideoInfo.AudioStream audioStream = null;
@@ -57,14 +49,7 @@ public class ActionBarHandler {
SharedPreferences defaultPreferences = null;
public static ActionBarHandler getHandler() {
if(handler == null) {
handler = new ActionBarHandler();
}
return handler;
}
class ForamatItemSelectListener implements ActionBar.OnNavigationListener {
class FormatItemSelectListener implements ActionBar.OnNavigationListener {
@Override
public boolean onNavigationItemSelected(int itemPosition, long itemId) {
selectFormatItem((int)itemId);
@@ -72,6 +57,10 @@ public class ActionBarHandler {
}
}
public ActionBarHandler(AppCompatActivity activity) {
this.activity = activity;
}
public void setupNavMenu(AppCompatActivity activity) {
this.activity = activity;
activity.getSupportActionBar().setNavigationMode(ActionBar.NAVIGATION_MODE_LIST);
@@ -80,14 +69,15 @@ public class ActionBarHandler {
public void setStreams(VideoInfo.VideoStream[] videoStreams, VideoInfo.AudioStream[] audioStreams) {
this.videoStreams = videoStreams;
selectedStream = 0;
defaultPreferences = PreferenceManager.getDefaultSharedPreferences(activity);
String[] itemArray = new String[videoStreams.length];
String defaultResolution = defaultPreferences
.getString(context.getString(R.string.defaultResolutionPreference),
context.getString(R.string.defaultResolutionListItem));
.getString(activity.getString(R.string.defaultResolutionPreference),
activity.getString(R.string.defaultResolutionListItem));
int defaultResolutionPos = 0;
for(int i = 0; i < videoStreams.length; i++) {
itemArray[i] = VideoInfo.getNameById(videoStreams[i].format) + " " + videoStreams[i].resolution;
itemArray[i] = MediaFormat.getNameById(videoStreams[i].format) + " " + videoStreams[i].resolution;
if(defaultResolution.equals(videoStreams[i].resolution)) {
defaultResolutionPos = i;
}
@@ -98,27 +88,25 @@ public class ActionBarHandler {
if(activity != null) {
ActionBar ab = activity.getSupportActionBar();
ab.setListNavigationCallbacks(itemAdapter
,new ForamatItemSelectListener());
,new FormatItemSelectListener());
ab.setSelectedNavigationItem(defaultResolutionPos);
}
// set audioStream
audioStream = null;
String preferedFormat = PreferenceManager.getDefaultSharedPreferences(context)
.getString(context.getString(R.string.defaultAudioFormatPreference), "webm");
String preferedFormat = defaultPreferences
.getString(activity.getString(R.string.defaultAudioFormatPreference), "webm");
if(preferedFormat.equals("webm")) {
for(VideoInfo.AudioStream s : audioStreams) {
if(s.format == VideoInfo.I_WEBMA) {
if(s.format == MediaFormat.WEBMA.id) {
audioStream = s;
}
}
} else if(preferedFormat.equals("m4a")){
for(VideoInfo.AudioStream s : audioStreams) {
Log.d(TAG, VideoInfo.getMimeById(s.format) + " : " + Integer.toString(s.bandWidth));
if(s.format == VideoInfo.I_M4A &&
(audioStream == null || audioStream.bandWidth > s.bandWidth)) {
if(s.format == MediaFormat.M4A.id &&
(audioStream == null || audioStream.bandwidth > s.bandwidth)) {
audioStream = s;
Log.d(TAG, "last choosen");
}
}
}
@@ -128,96 +116,90 @@ public class ActionBarHandler {
selectedStream = i;
}
public boolean setupMenu(Menu menu, MenuInflater inflater, Context context) {
this.context = context;
public boolean setupMenu(Menu menu, MenuInflater inflater) {
// CAUTION set item properties programmatically otherwise it would not be accepted by
// appcompat itemsinflater.inflate(R.menu.videoitem_detail, menu);
defaultPreferences = PreferenceManager.getDefaultSharedPreferences(context);
defaultPreferences = PreferenceManager.getDefaultSharedPreferences(activity);
inflater.inflate(R.menu.videoitem_detail, menu);
MenuItem playItem = menu.findItem(R.id.menu_item_play);
MenuItem shareItem = menu.findItem(R.id.menu_item_share);
MenuItem castItem = menu.findItem(R.id.action_play_with_kodi);
MenuItemCompat.setShowAsAction(playItem, MenuItemCompat.SHOW_AS_ACTION_ALWAYS
| MenuItemCompat.SHOW_AS_ACTION_WITH_TEXT);
MenuItemCompat.setShowAsAction(shareItem, MenuItemCompat.SHOW_AS_ACTION_IF_ROOM
| MenuItemCompat.SHOW_AS_ACTION_WITH_TEXT);
castItem.setVisible(defaultPreferences
.getBoolean(context.getString(R.string.showPlayWidthKodiPreference), false));
.getBoolean(activity.getString(R.string.showPlayWidthKodiPreference), false));
return true;
}
public boolean onItemSelected(MenuItem item, Context context) {
this.context = context;
public boolean onItemSelected(MenuItem item) {
int id = item.getItemId();
switch(id) {
case R.id.menu_item_play:
playVideo();
break;
case R.id.menu_item_share:
if(!videoTitle.isEmpty()) {
Intent intent = new Intent();
intent.setAction(Intent.ACTION_SEND);
intent.putExtra(Intent.EXTRA_TEXT, webisteUrl);
intent.putExtra(Intent.EXTRA_TEXT, websiteUrl);
intent.setType("text/plain");
context.startActivity(Intent.createChooser(intent, context.getString(R.string.shareDialogTitle)));
activity.startActivity(Intent.createChooser(intent, activity.getString(R.string.shareDialogTitle)));
}
break;
return true;
case R.id.menu_item_openInBrowser: {
openInBrowser();
}
break;
return true;
case R.id.menu_item_download:
downloadVideo();
break;
return true;
case R.id.action_settings: {
Intent intent = new Intent(context, SettingsActivity.class);
context.startActivity(intent);
Intent intent = new Intent(activity, SettingsActivity.class);
activity.startActivity(intent);
}
break;
case R.id.action_play_with_kodi:
playWithKodi();
break;
return true;
case R.id.menu_item_play_audio:
playAudio();
break;
return true;
default:
Log.e(TAG, "Menu Item not known");
}
return true;
return false;
}
public void setVideoInfo(String websiteUrl, String videoTitle) {
this.webisteUrl = websiteUrl;
this.websiteUrl = websiteUrl;
this.videoTitle = videoTitle;
}
public void playVideo() {
// ----------- THE MAGIC MOMENT ---------------
if(!videoTitle.isEmpty()) {
if (PreferenceManager.getDefaultSharedPreferences(context)
.getBoolean(context.getString(R.string.useExternalPlayer), false)) {
if (PreferenceManager.getDefaultSharedPreferences(activity)
.getBoolean(activity.getString(R.string.useExternalPlayer), false)) {
// External Player
Intent intent = new Intent();
try {
intent.setAction(Intent.ACTION_VIEW);
intent.setDataAndType(Uri.parse(videoStreams[selectedStream].url),
VideoInfo.getMimeById(videoStreams[selectedStream].format));
context.startActivity(intent); // HERE !!!
MediaFormat.getMimeById(videoStreams[selectedStream].format));
intent.putExtra(Intent.EXTRA_TITLE, videoTitle);
intent.putExtra("title", videoTitle);
activity.startActivity(intent); // HERE !!!
} catch (Exception e) {
e.printStackTrace();
AlertDialog.Builder builder = new AlertDialog.Builder(context);
AlertDialog.Builder builder = new AlertDialog.Builder(activity);
builder.setMessage(R.string.noPlayerFound)
.setPositiveButton(R.string.installStreamPlayer, new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
Intent intent = new Intent();
intent.setAction(Intent.ACTION_VIEW);
intent.setData(Uri.parse(context.getString(R.string.fdroidVLCurl)));
context.startActivity(intent);
intent.setData(Uri.parse(activity.getString(R.string.fdroidVLCurl)));
activity.startActivity(intent);
}
})
.setNegativeButton(R.string.cancel, new DialogInterface.OnClickListener() {
@@ -229,21 +211,21 @@ public class ActionBarHandler {
builder.create().show();
}
} else {
Intent intent = new Intent(context, PlayVideoActivity.class);
// Internal Player
Intent intent = new Intent(activity, PlayVideoActivity.class);
intent.putExtra(PlayVideoActivity.VIDEO_TITLE, videoTitle);
intent.putExtra(PlayVideoActivity.STREAM_URL, videoStreams[selectedStream].url);
intent.putExtra(PlayVideoActivity.VIDEO_URL, webisteUrl);
context.startActivity(intent);
intent.putExtra(PlayVideoActivity.VIDEO_URL, websiteUrl);
activity.startActivity(intent);
}
}
// --------------------------------------------
}
public void downloadVideo() {
Log.d(TAG, "bla");
if(!videoTitle.isEmpty()) {
String videoSuffix = "." + VideoInfo.getSuffixById(videoStreams[selectedStream].format);
String audioSuffix = "." + VideoInfo.getSuffixById(audioStream.format);
String videoSuffix = "." + MediaFormat.getSuffixById(videoStreams[selectedStream].format);
String audioSuffix = "." + MediaFormat.getSuffixById(audioStream.format);
Bundle args = new Bundle();
args.putString(DownloadDialog.FILE_SUFFIX_VIDEO, videoSuffix);
args.putString(DownloadDialog.FILE_SUFFIX_AUDIO, audioSuffix);
@@ -260,9 +242,9 @@ public class ActionBarHandler {
if(!videoTitle.isEmpty()) {
Intent intent = new Intent();
intent.setAction(Intent.ACTION_VIEW);
intent.setData(Uri.parse(webisteUrl));
intent.setData(Uri.parse(websiteUrl));
context.startActivity(Intent.createChooser(intent, context.getString(R.string.chooseBrowser)));
activity.startActivity(Intent.createChooser(intent, activity.getString(R.string.chooseBrowser)));
}
}
@@ -271,19 +253,19 @@ public class ActionBarHandler {
try {
Intent intent = new Intent(Intent.ACTION_VIEW);
intent.setPackage(KORE_PACKET);
intent.setData(Uri.parse(webisteUrl.replace("https", "http")));
context.startActivity(intent);
intent.setData(Uri.parse(websiteUrl.replace("https", "http")));
activity.startActivity(intent);
} catch (Exception e) {
e.printStackTrace();
AlertDialog.Builder builder = new AlertDialog.Builder(context);
AlertDialog.Builder builder = new AlertDialog.Builder(activity);
builder.setMessage(R.string.koreNotFound)
.setPositiveButton(R.string.installeKore, new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
Intent intent = new Intent();
intent.setAction(Intent.ACTION_VIEW);
intent.setData(Uri.parse(context.getString(R.string.fdroidKoreUrl)));
context.startActivity(intent);
intent.setData(Uri.parse(activity.getString(R.string.fdroidKoreUrl)));
activity.startActivity(intent);
}
})
.setNegativeButton(R.string.cancel, new DialogInterface.OnClickListener() {
@@ -302,19 +284,21 @@ public class ActionBarHandler {
try {
intent.setAction(Intent.ACTION_VIEW);
intent.setDataAndType(Uri.parse(audioStream.url),
VideoInfo.getMimeById(audioStream.format));
context.startActivity(intent); // HERE !!!
MediaFormat.getMimeById(audioStream.format));
intent.putExtra(Intent.EXTRA_TITLE, videoTitle);
intent.putExtra("title", videoTitle);
activity.startActivity(intent); // HERE !!!
} catch (Exception e) {
e.printStackTrace();
AlertDialog.Builder builder = new AlertDialog.Builder(context);
AlertDialog.Builder builder = new AlertDialog.Builder(activity);
builder.setMessage(R.string.noPlayerFound)
.setPositiveButton(R.string.installStreamPlayer, new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
Intent intent = new Intent();
intent.setAction(Intent.ACTION_VIEW);
intent.setData(Uri.parse(context.getString(R.string.fdroidVLCurl)));
context.startActivity(intent);
intent.setData(Uri.parse(activity.getString(R.string.fdroidVLCurl)));
activity.startActivity(intent);
}
})
.setNegativeButton(R.string.cancel, new DialogInterface.OnClickListener() {
@@ -324,6 +308,8 @@ public class ActionBarHandler {
}
});
builder.create().show();
Log.e(TAG, "Either no Streaming player for audio was installed, or something important crashed:");
e.printStackTrace();
}
}
}

View File

@@ -1,9 +1,11 @@
package org.schabi.newpipe;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.net.HttpURLConnection;
import java.net.URL;
import java.net.UnknownHostException;
/**
* Created by Christian Schabesberger on 14.08.15.
@@ -28,12 +30,25 @@ import java.net.URL;
public class Downloader {
private static final String USER_AGENT = "Mozilla/5.0";
public static String download(String siteUrl) {
StringBuffer response = new StringBuffer();
public static String download(String siteUrl, String language) {
String ret = "";
try {
URL url = new URL(siteUrl);
HttpURLConnection con = (HttpURLConnection) url.openConnection();
con.setRequestProperty("Accept-Language", language);
ret = dl(con);
}
catch(Exception e) {
e.printStackTrace();
}
return ret;
}
private static String dl(HttpURLConnection con) {
StringBuffer response = new StringBuffer();
try {
con.setRequestMethod("GET");
con.setRequestProperty("User-Agent", USER_AGENT);
@@ -45,9 +60,31 @@ public class Downloader {
response.append(inputLine);
}
in.close();
} catch (Exception e) {
}
catch(UnknownHostException uhe) {//thrown when there's no internet connection
uhe.printStackTrace();
//Toast.makeText(getActivity(), uhe.getMessage(), Toast.LENGTH_LONG).show();
}
catch (Exception e) {
e.printStackTrace();
}
return response.toString();
}
public static String download(String siteUrl) {
String ret = "";
try {
URL url = new URL(siteUrl);
HttpURLConnection con = (HttpURLConnection) url.openConnection();
ret = dl(con);
}
catch(Exception e) {
e.printStackTrace();
}
return ret;
}
}

View File

@@ -1,7 +1,5 @@
package org.schabi.newpipe;
import android.graphics.Bitmap;
/**
* Created by Christian Schabesberger on 10.08.15.
*

View File

@@ -0,0 +1,64 @@
package org.schabi.newpipe;
/**
* Created by Adam Howard on 08/11/15.
*
* Copyright (c) Christian Schabesberger <chris.schabesberger@mailbox.org>
* and Adam Howard <achdisposable1@gmail.com> 2015
*
* 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/>.
*/
public enum MediaFormat {
// id name suffix mime type
MPEG_4 (0x0, "MPEG-4", "mp4", "video/mp4"),
v3GPP (0x1, "3GPP", "3gp", "video/3gpp"),
WEBM (0x2, "WebM", "webm", "video/webm"),
M4A (0x3, "m4a", "m4a", "audio/mp4"),
WEBMA (0x4, "WebM", "webm", "audio/webm");
public final int id;
public final String name;
public final String suffix;
public final String mimeType;
MediaFormat(int id, String name, String suffix, String mimeType) {
this.id = id;
this.name = name;
this.suffix = suffix;
this.mimeType = mimeType;
}
public static String getNameById(int ident) {
for (MediaFormat vf : MediaFormat.values()) {
if(vf.id == ident) return vf.name;
}
return "";
}
public static String getSuffixById(int ident) {
for (MediaFormat vf : MediaFormat.values()) {
if(vf.id == ident) return vf.suffix;
}
return "";
}
public static String getMimeById(int ident) {
for (MediaFormat vf : MediaFormat.values()) {
if(vf.id == ident) return vf.mimeType;
}
return "";
}
}

View File

@@ -1,9 +1,10 @@
package org.schabi.newpipe;
import android.content.Context;
import android.content.Intent;
import android.content.SharedPreferences;
import android.content.pm.ActivityInfo;
import android.content.res.Configuration;
import android.content.res.Resources;
import android.media.MediaPlayer;
import android.net.Uri;
import android.os.Build;
@@ -17,15 +18,11 @@ import android.view.Display;
import android.view.Menu;
import android.view.MenuInflater;
import android.view.MenuItem;
import android.view.Surface;
import android.view.View;
import android.view.WindowManager;
import android.widget.Button;
import android.widget.FrameLayout;
import android.widget.LinearLayout;
import android.widget.MediaController;
import android.widget.ProgressBar;
import android.widget.SeekBar;
import android.widget.VideoView;
/**
@@ -72,6 +69,9 @@ public class PlayVideoActivity extends AppCompatActivity {
private boolean isLandscape = true;
private boolean hasSoftKeys = false;
private SharedPreferences prefs;
private static final String PREF_IS_LANDSCAPE = "is_landscape";
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
@@ -79,7 +79,7 @@ public class PlayVideoActivity extends AppCompatActivity {
setContentView(R.layout.activity_play_video);
isLandscape = checkIfLandscape();
hasSoftKeys = checkIfhasSoftKeys();
hasSoftKeys = checkIfHasSoftKeys();
actionBar = getSupportActionBar();
actionBar.setDisplayHomeAsUpEnabled(true);
@@ -133,15 +133,21 @@ public class PlayVideoActivity extends AppCompatActivity {
}
});
decorView.setSystemUiVisibility(View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN
| View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION
| View.SYSTEM_UI_FLAG_LAYOUT_STABLE);
if (android.os.Build.VERSION.SDK_INT >= 17) {
decorView.setSystemUiVisibility(View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN
| View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION
| View.SYSTEM_UI_FLAG_LAYOUT_STABLE);
}
prefs = getPreferences(Context.MODE_PRIVATE);
if(prefs.getBoolean(PREF_IS_LANDSCAPE, false) && !isLandscape) {
toggleOrientation();
}
}
@Override
protected void onPostCreate(Bundle savedInstanceState) {
super.onPostCreate(savedInstanceState);
}
@Override
@@ -179,14 +185,7 @@ public class PlayVideoActivity extends AppCompatActivity {
startActivity(Intent.createChooser(intent, getString(R.string.shareDialogTitle)));
break;
case R.id.menu_item_screen_rotation:
Display display = ((WindowManager) getSystemService(WINDOW_SERVICE)).getDefaultDisplay();
if(display.getRotation() == Surface.ROTATION_0
|| display.getRotation() == Surface.ROTATION_180) {
setRequestedOrientation (ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE);
} else if(display.getRotation() == Surface.ROTATION_90
|| display.getRotation() == Surface.ROTATION_270) {
setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_PORTRAIT);
}
toggleOrientation();
break;
default:
Log.e(TAG, "Error: MenuItem not known");
@@ -201,10 +200,10 @@ public class PlayVideoActivity extends AppCompatActivity {
if (config.orientation == Configuration.ORIENTATION_LANDSCAPE) {
isLandscape = true;
adjustMediaControllMetrics();
adjustMediaControlMetrics();
} else if (config.orientation == Configuration.ORIENTATION_PORTRAIT){
isLandscape = false;
adjustMediaControllMetrics();
adjustMediaControlMetrics();
}
}
@@ -227,7 +226,7 @@ public class PlayVideoActivity extends AppCompatActivity {
uiIsHidden = false;
mediaController.show(100000);
actionBar.show();
adjustMediaControllMetrics();
adjustMediaControlMetrics();
getWindow().clearFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN);
Handler handler = new Handler();
handler.postDelayed(new Runnable() {
@@ -248,16 +247,18 @@ public class PlayVideoActivity extends AppCompatActivity {
uiIsHidden = true;
actionBar.hide();
mediaController.hide();
decorView.setSystemUiVisibility(View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN
| View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION
| View.SYSTEM_UI_FLAG_FULLSCREEN
| View.SYSTEM_UI_FLAG_HIDE_NAVIGATION
| View.SYSTEM_UI_FLAG_LAYOUT_STABLE);
if (android.os.Build.VERSION.SDK_INT >= 17) {
decorView.setSystemUiVisibility(View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN
| View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION
| View.SYSTEM_UI_FLAG_FULLSCREEN
| View.SYSTEM_UI_FLAG_HIDE_NAVIGATION
| View.SYSTEM_UI_FLAG_LAYOUT_STABLE);
}
getWindow().setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN,
WindowManager.LayoutParams.FLAG_FULLSCREEN);
}
private void adjustMediaControllMetrics() {
private void adjustMediaControlMetrics() {
MediaController.LayoutParams mediaControllerLayout
= new MediaController.LayoutParams(MediaController.LayoutParams.MATCH_PARENT,
MediaController.LayoutParams.WRAP_CONTENT);
@@ -272,7 +273,7 @@ public class PlayVideoActivity extends AppCompatActivity {
mediaController.setLayoutParams(mediaControllerLayout);
}
private boolean checkIfhasSoftKeys(){
private boolean checkIfHasSoftKeys(){
if(Build.VERSION.SDK_INT >= 17) {
return getNavigationBarHeight() != 0 || getNavigationBarWidth() != 0;
} else {
@@ -319,4 +320,17 @@ public class PlayVideoActivity extends AppCompatActivity {
getWindowManager().getDefaultDisplay().getMetrics(displayMetrics);
return displayMetrics.heightPixels < displayMetrics.widthPixels;
}
private void toggleOrientation() {
if(isLandscape) {
isLandscape = false;
setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_PORTRAIT);
} else {
isLandscape = true;
setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_SENSOR_LANDSCAPE);
}
SharedPreferences.Editor editor = prefs.edit();
editor.putBoolean(PREF_IS_LANDSCAPE, isLandscape);
editor.commit();
}
}

View File

@@ -1,7 +1,6 @@
package org.schabi.newpipe;
import android.graphics.Bitmap;
import java.util.ArrayList;
import java.util.Vector;
/**
@@ -33,5 +32,8 @@ public interface SearchEngine {
public Vector<VideoInfoItem> resultList = new Vector<>();
}
Result search(String query, int page);
ArrayList<String> suggestionList(String query);
//Result search(String query, int page);
Result search(String query, int page, String contentCountry);
}

View File

@@ -25,11 +25,11 @@ public interface StreamingService {
public String name = "";
}
ServiceInfo getServiceInfo();
Class getExtractorClass();
Class getSearchEngineClass();
Extractor getExtractorInstance();
SearchEngine getSearchEngineInstance();
// When a VIEW_ACTION is caught this function will test if the url delivered within the calling
// Intent was meant to be watched with this Service.
// Return false if this service shall not allow to be callean through ACTIONs.
/**When a VIEW_ACTION is caught this function will test if the url delivered within the calling
Intent was meant to be watched with this Service.
Return false if this service shall not allow to be callean through ACTIONs.*/
boolean acceptUrl(String videoUrl);
}

View File

@@ -1,5 +1,11 @@
package org.schabi.newpipe;
import android.graphics.Bitmap;
import android.util.Log;
import java.util.Date;
import java.util.Vector;
/**
* Created by Christian Schabesberger on 26.08.15.
*
@@ -20,85 +26,36 @@ package org.schabi.newpipe;
* along with NewPipe. If not, see <http://www.gnu.org/licenses/>.
*/
import android.graphics.Bitmap;
import android.util.Log;
import java.util.Vector;
/**Info object for opened videos, ie the video ready to play.*/
public class VideoInfo {
public String id = "";
public String title = "";
public String uploader = "";
public String thumbnail_url = "";
public Bitmap thumbnail = null;
public String webpage_url = "";
public String upload_date = "";
public long view_count = 0;
public String uploader_thumbnail_url = "";
public Bitmap uploader_thumbnail = null;
public String description = "";
public int duration = -1;
public int age_limit = 0;
public int like_count = 0;
public int dislike_count = 0;
public String average_rating = "";
public VideoStream[] videoStreams = null;
public AudioStream[] audioStreams = null;
public VideoInfoItem nextVideo = null;
public VideoInfoItem[] relatedVideos = null;
public int videoAvailableStatus = VIDEO_AVAILABLE;
private static final String TAG = VideoInfo.class.toString();
// format identifier
public static final int I_MPEG_4 = 0x0;
public static final int I_3GPP = 0x1;
public static final int I_WEBM = 0x2;
public static final int I_M4A = 0x3;
public static final int I_WEBMA = 0x4;
// format name
public static final String F_MPEG_4 = "MPEG-4";
public static final String F_3GPP = "3GPP";
public static final String F_WEBM = "WebM";
public static final String F_M4A = "m4a";
public static final String F_WEBMA = "WebM";
// file suffix
public static final String C_MPEG_4 = "mp4";
public static final String C_3GPP = "3gp";
public static final String C_WEBM = "webm";
public static final String C_M4A = "m4a";
public static final String C_WEBMA = "webm";
// mimeType
public static final String M_MPEG_4 = "video/mp4";
public static final String M_3GPP = "video/3gpp";
public static final String M_WEBM = "video/webm";
public static final String M_M4A = "audio/mp4";
public static final String M_WEBMA = "audio/webm";
public static final int VIDEO_AVAILABLE = 0x00;
public static final int VIDEO_UNAVAILABLE = 0x01;
public static final int VIDEO_UNAVAILABLE_GEMA = 0x02;
public static String getNameById(int id) {
switch(id) {
case I_MPEG_4: return F_MPEG_4;
case I_3GPP: return F_3GPP;
case I_WEBM: return F_WEBM;
case I_M4A: return F_M4A;
case I_WEBMA: return F_WEBMA;
default: Log.e(TAG, "format not known: " +
Integer.toString(id) + "call the programmer he messed it up.");
}
return "";
}
public static String getSuffixById(int id) {
switch(id) {
case I_MPEG_4: return C_MPEG_4;
case I_3GPP: return C_3GPP;
case I_WEBM: return C_WEBM;
case I_M4A: return C_M4A;
case I_WEBMA: return C_WEBMA;
default: Log.e(TAG, "format not known: " +
Integer.toString(id) + "call the programmer he messed it up.");
}
return "";
}
public static String getMimeById(int id) {
switch(id) {
case I_MPEG_4: return M_MPEG_4;
case I_3GPP: return M_3GPP;
case I_WEBM: return M_WEBM;
case I_M4A: return M_M4A;
case I_WEBMA: return M_WEBMA;
default: Log.e(TAG, "format not known: " +
Integer.toString(id) + "call the programmer he messed it up.");
}
return "";
}
public static final int VIDEO_UNAVAILABLE_GEMA = 0x02;//German DRM organisation
public static class VideoStream {
public VideoStream(String url, int format, String res) {
@@ -110,36 +67,13 @@ public class VideoInfo {
}
public static class AudioStream {
public AudioStream(String url, int format, int bandWidth, int samplingRate) {
public AudioStream(String url, int format, int bandwidth, int samplingRate) {
this.url = url; this.format = format;
this.bandWidth = bandWidth; this.samplingRate = samplingRate;
this.bandwidth = bandwidth; this.samplingRate = samplingRate;
}
public String url = "";
public int format = -1;
public int bandWidth = -1;
public int bandwidth = -1;
public int samplingRate = -1;
}
public String id = "";
public String uploader = "";
public String upload_date = "";
public String uploader_thumbnail_url = "";
public Bitmap uploader_thumbnail = null;
public String title = "";
public String thumbnail_url = "";
public Bitmap thumbnail = null;
public String description = "";
public int duration = -1;
public int age_limit = 0;
public String webpage_url = "";
public String view_count = "";
public String like_count = "";
public String dislike_count = "";
public String average_rating = "";
public VideoStream[] videoStreams = null;
public AudioStream[] audioStreams = null;
public VideoInfoItem nextVideo = null;
public Vector<VideoInfoItem> relatedVideos = null;
public int videoAvailableStatus = VIDEO_AVAILABLE;
}

View File

@@ -1,6 +1,8 @@
package org.schabi.newpipe;
import android.graphics.Bitmap;
import android.os.Parcel;
import android.os.Parcelable;
/**
* Created by Christian Schabesberger on 26.08.15.
@@ -22,13 +24,63 @@ import android.graphics.Bitmap;
* along with NewPipe. If not, see <http://www.gnu.org/licenses/>.
*/
public class VideoInfoItem {
/**Info object for previews of unopened videos, eg search results, related videos*/
public class VideoInfoItem implements Parcelable {
public String id = "";
public String title = "";
public String uploader = "";
public String duration = "";
public String thumbnail_url = "";
public Bitmap thumbnail = null;
public String webpage_url = "";
public String upload_date = "";
public String view_count = "";
public String duration = "";
protected VideoInfoItem(Parcel in) {
id = in.readString();
title = in.readString();
uploader = in.readString();
duration = in.readString();
thumbnail_url = in.readString();
thumbnail = (Bitmap) in.readValue(Bitmap.class.getClassLoader());
webpage_url = in.readString();
upload_date = in.readString();
view_count = in.readString();
}
public VideoInfoItem() {
}
@Override
public int describeContents() {
return 0;
}
@Override
public void writeToParcel(Parcel dest, int flags) {
dest.writeString(id);
dest.writeString(title);
dest.writeString(uploader);
dest.writeString(duration);
dest.writeString(thumbnail_url);
dest.writeValue(thumbnail);
dest.writeString(webpage_url);
dest.writeString(upload_date);
dest.writeString(view_count);
}
@SuppressWarnings("unused")
public static final Parcelable.Creator<VideoInfoItem> CREATOR = new Parcelable.Creator<VideoInfoItem>() {
@Override
public VideoInfoItem createFromParcel(Parcel in) {
return new VideoInfoItem(in);
}
@Override
public VideoInfoItem[] newArray(int size) {
return new VideoInfoItem[size];
}
};
}

View File

@@ -0,0 +1,76 @@
package org.schabi.newpipe;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ImageView;
import android.widget.TextView;
/**
* Created by Christian Schabesberger on 24.10.15.
*
* Copyright (C) Christian Schabesberger 2015 <chris.schabesberger@mailbox.org>
* VideoInfoItemViewCreator.java is part of NewPipe.
*
* NewPipe is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* NewPipe is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with NewPipe. If not, see <http://www.gnu.org/licenses/>.
*/
public class VideoInfoItemViewCreator {
private static final String TAG = VideoInfoItemViewCreator.class.toString();
LayoutInflater inflater;
public VideoInfoItemViewCreator(LayoutInflater inflater) {
this.inflater = inflater;
}
public View getViewByVideoInfoItem(View convertView, ViewGroup parent, VideoInfoItem info) {
ViewHolder 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);
convertView.setTag(holder);
} else {
holder = (ViewHolder) convertView.getTag();
}
if(info.thumbnail == null) {
holder.itemThumbnailView.setImageResource(R.drawable.dummy_thumbnail);
} else {
holder.itemThumbnailView.setImageBitmap(info.thumbnail);
}
holder.itemVideoTitleView.setText(info.title);
holder.itemUploaderView.setText(info.uploader);
holder.itemDurationView.setText(info.duration);
if(!info.upload_date.isEmpty()) {
holder.itemUploadDateView.setText(info.upload_date);
} else {
//tweak if necessary: This is a hack to prevent having white space in the layout :P
holder.itemUploadDateView.setText(info.view_count);
}
return convertView;
}
private class ViewHolder {
public ImageView itemThumbnailView;
public TextView itemVideoTitleView, itemUploaderView, itemDurationView, itemUploadDateView;
}
}

View File

@@ -1,22 +1,15 @@
package org.schabi.newpipe;
import android.content.ContentProviderOperation;
import android.content.res.Configuration;
import android.os.Build;
import android.preference.PreferenceManager;
import android.support.v7.app.ActionBar;
import android.support.v7.app.AppCompatActivity;
import android.content.Intent;
import android.os.Bundle;
import android.preference.PreferenceManager;
import android.support.v4.app.NavUtils;
import android.util.DisplayMetrics;
import android.support.v7.app.AppCompatActivity;
import android.util.Log;
import android.view.Menu;
import android.view.MenuInflater;
import android.view.MenuItem;
import android.view.WindowManager;
import org.schabi.newpipe.youtube.YoutubeExtractor;
import android.widget.Toast;
/**
@@ -41,9 +34,10 @@ public class VideoItemDetailActivity extends AppCompatActivity {
private static final String TAG = VideoItemDetailActivity.class.toString();
VideoItemDetailFragment fragment;
private String videoUrl;
private int currentStreamingService = -1;
private boolean isLandscape;
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
@@ -51,7 +45,6 @@ public class VideoItemDetailActivity extends AppCompatActivity {
// Show the Up button in the action bar.
getSupportActionBar().setDisplayHomeAsUpEnabled(true);
ActionBarHandler.getHandler().setupNavMenu(this);
// savedInstanceState is non-null when there is fragment state
// saved from previous configurations of this activity
@@ -68,6 +61,7 @@ public class VideoItemDetailActivity extends AppCompatActivity {
// this means the video was called though another app
if (getIntent().getData() != null) {
videoUrl = getIntent().getData().toString();
Log.i(TAG, "video URL passed:\"" + videoUrl + "\"");
StreamingService[] serviceList = ServiceList.getServices();
Extractor extractor = null;
for (int i = 0; i < serviceList.length; i++) {
@@ -75,14 +69,17 @@ public class VideoItemDetailActivity extends AppCompatActivity {
arguments.putInt(VideoItemDetailFragment.STREAMING_SERVICE, i);
try {
currentStreamingService = i;
extractor = (Extractor) ServiceList.getService(i)
.getExtractorClass().newInstance();
extractor = ServiceList.getService(i).getExtractorInstance();
} catch (Exception e) {
e.printStackTrace();
}
break;
}
}
if(extractor == null) {
Toast.makeText(this, R.string.urlNotSupportedText, Toast.LENGTH_LONG)
.show();
}
arguments.putString(VideoItemDetailFragment.VIDEO_URL,
extractor.getVideoUrl(extractor.getVideoId(videoUrl)));
arguments.putBoolean(VideoItemDetailFragment.AUTO_PLAY,
@@ -95,14 +92,27 @@ public class VideoItemDetailActivity extends AppCompatActivity {
arguments.putInt(VideoItemDetailFragment.STREAMING_SERVICE, currentStreamingService);
arguments.putBoolean(VideoItemDetailFragment.AUTO_PLAY, false);
}
// Create the detail fragment and add it to the activity
// using a fragment transaction.
VideoItemDetailFragment fragment = new VideoItemDetailFragment();
fragment.setArguments(arguments);
getSupportFragmentManager().beginTransaction()
.add(R.id.videoitem_detail_container, fragment)
.commit();
} else {
videoUrl = savedInstanceState.getString(VideoItemDetailFragment.VIDEO_URL);
currentStreamingService = savedInstanceState.getInt(VideoItemDetailFragment.STREAMING_SERVICE);
arguments = savedInstanceState;
}
// Create the detail fragment and add it to the activity
// using a fragment transaction.
fragment = new VideoItemDetailFragment();
fragment.setArguments(arguments);
getSupportFragmentManager().beginTransaction()
.add(R.id.videoitem_detail_container, fragment)
.commit();
}
@Override
public void onSaveInstanceState(Bundle outState) {
outState.putString(VideoItemDetailFragment.VIDEO_URL, videoUrl);
outState.putInt(VideoItemDetailFragment.STREAMING_SERVICE, currentStreamingService);
outState.putBoolean(VideoItemDetailFragment.AUTO_PLAY, false);
}
@Override
@@ -113,25 +123,23 @@ public class VideoItemDetailActivity extends AppCompatActivity {
// activity, the Up button is shown. Use NavUtils to allow users
// to navigate up one level in the application structure. For
// more details, see the Navigation pattern on Android Design:
//
// http://developer.android.com/design/patterns/navigation.html#up-vs-back
//
Intent intent = new Intent(this, VideoItemListActivity.class);
intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
NavUtils.navigateUpTo(this, intent);
return true;
} else {
ActionBarHandler.getHandler().onItemSelected(item, this);
return fragment.onOptionsItemSelected(item) ||
super.onOptionsItemSelected(item);
}
return super.onOptionsItemSelected(item);
}
@Override
public boolean onCreatePanelMenu(int featured, Menu menu) {
super.onCreatePanelMenu(featured, menu);
MenuInflater inflater = getMenuInflater();
ActionBarHandler.getHandler().setupMenu(menu, inflater, this);
public boolean onCreateOptionsMenu(Menu menu) {
super.onCreateOptionsMenu(menu);
fragment.onCreateOptionsMenu(menu, getMenuInflater());
return true;
}
}

View File

@@ -1,31 +1,40 @@
package org.schabi.newpipe;
import android.app.Activity;
import android.content.Intent;
import android.content.SharedPreferences;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.media.Image;
import android.os.Bundle;
import android.os.Handler;
import android.preference.PreferenceManager;
import android.support.design.widget.FloatingActionButton;
import android.support.v4.app.Fragment;
import android.support.v7.app.ActionBar;
import android.support.v7.app.AppCompatActivity;
import android.text.Html;
import android.text.method.LinkMovementMethod;
import android.util.DisplayMetrics;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.Menu;
import android.view.MenuInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.GridLayout;
import android.widget.Button;
import android.widget.FrameLayout;
import android.widget.ImageView;
import android.widget.ListView;
import android.widget.ProgressBar;
import android.widget.RelativeLayout;
import android.widget.ScrollView;
import android.widget.TextView;
import android.view.MenuItem;
import java.net.URL;
import java.text.DateFormat;
import java.text.NumberFormat;
import java.util.Calendar;
import java.util.Date;
import java.util.Locale;
import java.util.Vector;
@@ -60,21 +69,32 @@ public class VideoItemDetailFragment extends Fragment {
public static final String STREAMING_SERVICE = "streaming_service";
public static final String AUTO_PLAY = "auto_play";
private AppCompatActivity activity;
private ActionBarHandler actionBarHandler;
private boolean autoPlayEnabled = false;
private Thread extractorThread = null;
private VideoInfo currentVideoInfo = null;
private boolean showNextVideoItem = false;
public interface OnInvokeCreateOptionsMenuListener {
void createOptionsMenu();
}
private OnInvokeCreateOptionsMenuListener onInvokeCreateOptionsMenuListener = null;
private class ExtractorRunnable implements Runnable {
private Handler h = new Handler();
private Class extractorClass;
private Extractor extractor;
private String videoUrl;
public ExtractorRunnable(String videoUrl, Class extractorClass, VideoItemDetailFragment f) {
this.extractorClass = extractorClass;
public ExtractorRunnable(String videoUrl, Extractor extractor, VideoItemDetailFragment f) {
this.extractor = extractor;
this.videoUrl = videoUrl;
}
@Override
public void run() {
try {
Extractor extractor = (Extractor) extractorClass.newInstance();
VideoInfo videoInfo = extractor.getVideoInfo(videoUrl);
h.post(new VideoResultReturnedRunnable(videoInfo));
if (videoInfo.videoAvailableStatus == VideoInfo.VIDEO_AVAILABLE) {
@@ -82,12 +102,22 @@ public class VideoItemDetailFragment extends Fragment {
BitmapFactory.decodeStream(
new URL(videoInfo.thumbnail_url)
.openConnection()
.getInputStream()), SetThumbnailRunnable.VIDEO_THUMBNAIL));
.getInputStream()),
SetThumbnailRunnable.VIDEO_THUMBNAIL));
h.post(new SetThumbnailRunnable(
BitmapFactory.decodeStream(
new URL(videoInfo.uploader_thumbnail_url)
.openConnection()
.getInputStream()), SetThumbnailRunnable.CHANNEL_THUMBNAIL));
.getInputStream()),
SetThumbnailRunnable.CHANNEL_THUMBNAIL));
if(showNextVideoItem) {
h.post(new SetThumbnailRunnable(
BitmapFactory.decodeStream(
new URL(videoInfo.nextVideo.thumbnail_url)
.openConnection()
.getInputStream()),
SetThumbnailRunnable.NEXT_VIDEO_THUMBNAIL));
}
}
} catch (Exception e) {
e.printStackTrace();
@@ -103,13 +133,16 @@ public class VideoItemDetailFragment extends Fragment {
}
@Override
public void run() {
//todo: fix expired thread error:
// If the thread calling this runnable is expired, the following function will crash.
updateInfo(videoInfo);
}
}
private class SetThumbnailRunnable implements Runnable {
public static final int CHANNEL_THUMBNAIL = 2;
public static final int VIDEO_THUMBNAIL = 1;
public static final int CHANNEL_THUMBNAIL = 2;
public static final int NEXT_VIDEO_THUMBNAIL = 3;
private Bitmap thumbnail;
private int thumbnailId;
public SetThumbnailRunnable(Bitmap thumbnail, int id) {
@@ -128,72 +161,96 @@ public class VideoItemDetailFragment extends Fragment {
try {
switch (id) {
case SetThumbnailRunnable.VIDEO_THUMBNAIL:
thumbnailView = (ImageView) a.findViewById(R.id.detailThumbnailView);
break;
case SetThumbnailRunnable.CHANNEL_THUMBNAIL:
thumbnailView = (ImageView) a.findViewById(R.id.detailUploaderThumbnailView);
break;
case SetThumbnailRunnable.NEXT_VIDEO_THUMBNAIL:
FrameLayout nextVideoFrame = (FrameLayout) a.findViewById(R.id.detailNextVideoFrame);
thumbnailView = (ImageView) nextVideoFrame.findViewById(R.id.itemThumbnailView);
currentVideoInfo.nextVideo.thumbnail = thumbnail;
break;
default:
Log.d(TAG, "Error: Thumbnail id not known");
return;
}
if (thumbnailView != null) {
thumbnailView.setImageBitmap(thumbnail);
}
} catch (java.lang.NullPointerException e) {
// No god programm design i know. :/
// Not good program design, I know. :/
Log.w(TAG, "updateThumbnail(): Fragment closed before thread ended work");
}
}
public void updateInfo(VideoInfo info) {
Activity a = getActivity();
currentVideoInfo = info;
try {
ProgressBar progressBar = (ProgressBar) a.findViewById(R.id.detailProgressBar);
TextView videoTitleView = (TextView) a.findViewById(R.id.detailVideoTitleView);
TextView uploaderView = (TextView) a.findViewById(R.id.detailUploaderView);
TextView viewCountView = (TextView) a.findViewById(R.id.detailViewCountView);
TextView thumbsUpView = (TextView) a.findViewById(R.id.detailThumbsUpCountView);
TextView thumbsDownView = (TextView) a.findViewById(R.id.detailThumbsDownCountView);
TextView uploadDateView = (TextView) a.findViewById(R.id.detailUploadDateView);
TextView descriptionView = (TextView) a.findViewById(R.id.detailDescriptionView);
ImageView thumbnailView = (ImageView) a.findViewById(R.id.detailThumbnailView);
ImageView uploaderThumbnailView = (ImageView) a.findViewById(R.id.detailUploaderThumbnailView);
ImageView thumbsUpPic = (ImageView) a.findViewById(R.id.detailThumbsUpImgView);
ImageView thumbsDownPic = (ImageView) a.findViewById(R.id.detailThumbsDownImgView);
View textSeperationLine = a.findViewById(R.id.textSeperationLine);
VideoInfoItemViewCreator videoItemViewCreator =
new VideoInfoItemViewCreator(LayoutInflater.from(getActivity()));
ScrollView contentMainView = (ScrollView) activity.findViewById(R.id.detailMainContent);
ProgressBar progressBar = (ProgressBar) activity.findViewById(R.id.detailProgressBar);
TextView videoTitleView = (TextView) activity.findViewById(R.id.detailVideoTitleView);
TextView uploaderView = (TextView) activity.findViewById(R.id.detailUploaderView);
TextView viewCountView = (TextView) activity.findViewById(R.id.detailViewCountView);
TextView thumbsUpView = (TextView) activity.findViewById(R.id.detailThumbsUpCountView);
TextView thumbsDownView = (TextView) activity.findViewById(R.id.detailThumbsDownCountView);
TextView uploadDateView = (TextView) activity.findViewById(R.id.detailUploadDateView);
TextView descriptionView = (TextView) activity.findViewById(R.id.detailDescriptionView);
ImageView thumbnailView = (ImageView) activity.findViewById(R.id.detailThumbnailView);
FrameLayout nextVideoFrame = (FrameLayout) activity.findViewById(R.id.detailNextVideoFrame);
RelativeLayout nextVideoRootFrame =
(RelativeLayout) activity.findViewById(R.id.detailNextVideoRootLayout);
View nextVideoView = videoItemViewCreator
.getViewByVideoInfoItem(null, nextVideoFrame, info.nextVideo);
nextVideoFrame.addView(nextVideoView);
Button nextVideoButton = (Button) activity.findViewById(R.id.detailNextVideoButton);
Button similarVideosButton = (Button) activity.findViewById(R.id.detailShowSimilarButton);
if(textSeperationLine != null) {
textSeperationLine.setVisibility(View.VISIBLE);
}
contentMainView.setVisibility(View.VISIBLE);
progressBar.setVisibility(View.GONE);
videoTitleView.setVisibility(View.VISIBLE);
uploaderView.setVisibility(View.VISIBLE);
uploadDateView.setVisibility(View.VISIBLE);
viewCountView.setVisibility(View.VISIBLE);
thumbsUpView.setVisibility(View.VISIBLE);
thumbsDownView.setVisibility(View.VISIBLE);
uploadDateView.setVisibility(View.VISIBLE);
descriptionView.setVisibility(View.VISIBLE);
thumbnailView.setVisibility(View.VISIBLE);
uploaderThumbnailView.setVisibility(View.VISIBLE);
thumbsUpPic.setVisibility(View.VISIBLE);
thumbsDownPic.setVisibility(View.VISIBLE);
if(!showNextVideoItem) {
nextVideoRootFrame.setVisibility(View.GONE);
similarVideosButton.setVisibility(View.GONE);
}
switch (info.videoAvailableStatus) {
case VideoInfo.VIDEO_AVAILABLE: {
videoTitleView.setText(info.title);
uploaderView.setText(info.uploader);
viewCountView.setText(info.view_count + " " + a.getString(R.string.viewSufix));
thumbsUpView.setText(info.like_count);
thumbsDownView.setText(info.dislike_count);
uploadDateView.setText(a.getString(R.string.uploadDatePrefix) + " " + info.upload_date);
Locale locale = getPreferredLocale();
NumberFormat nf = NumberFormat.getInstance(locale);
String localisedViewCount = nf.format(info.view_count);
viewCountView.setText(localisedViewCount
+ " " + activity.getString(R.string.viewSufix));
thumbsUpView.setText(nf.format(info.like_count));
thumbsDownView.setText(nf.format(info.dislike_count));
//this is horribly convoluted
//TODO: find a better way to convert YYYY-MM-DD to a locale-specific date
//suggestions welcome
int year = Integer.parseInt(info.upload_date.substring(0, 4));
int month = Integer.parseInt(info.upload_date.substring(5, 7));
int date = Integer.parseInt(info.upload_date.substring(8, 10));
Calendar cal = Calendar.getInstance();
cal.set(year, month, date);
Date datum = cal.getTime();
DateFormat df = DateFormat.getDateInstance(DateFormat.MEDIUM, locale);
String localisedDate = df.format(datum);
uploadDateView.setText(
activity.getString(R.string.uploadDatePrefix) + " " + localisedDate);
descriptionView.setText(Html.fromHtml(info.description));
descriptionView.setMovementMethod(LinkMovementMethod.getInstance());
ActionBarHandler.getHandler().setVideoInfo(info.webpage_url, info.title);
actionBarHandler.setVideoInfo(info.webpage_url, info.title);
// parse streams
Vector<VideoInfo.VideoStream> streamsToUse = new Vector<>();
@@ -206,21 +263,38 @@ public class VideoItemDetailFragment extends Fragment {
for (int i = 0; i < streamList.length; i++) {
streamList[i] = streamsToUse.get(i);
}
ActionBarHandler.getHandler().setStreams(streamList, info.audioStreams);
actionBarHandler.setStreams(streamList, info.audioStreams);
}
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, currentVideoInfo.nextVideo.webpage_url);
//todo: make id dynamic the following line is crap
detailIntent.putExtra(VideoItemDetailFragment.STREAMING_SERVICE, 0);
startActivity(detailIntent);
}
});
break;
case VideoInfo.VIDEO_UNAVAILABLE_GEMA:
thumbnailView.setImageBitmap(BitmapFactory.decodeResource(getResources(), R.drawable.gruese_die_gema_unangebracht));
thumbnailView.setImageBitmap(BitmapFactory.decodeResource(
getResources(), R.drawable.gruese_die_gema_unangebracht));
break;
case VideoInfo.VIDEO_UNAVAILABLE:
thumbnailView.setImageBitmap(BitmapFactory.decodeResource(getResources(), R.drawable.not_available_monkey));
thumbnailView.setImageBitmap(BitmapFactory.decodeResource(
getResources(), R.drawable.not_available_monkey));
break;
default:
Log.e(TAG, "Video Availeble Status not known.");
Log.e(TAG, "Video Available Status not known.");
}
if(autoPlayEnabled) {
ActionBarHandler.getHandler().playVideo();
actionBarHandler.playVideo();
}
} catch (java.lang.NullPointerException e) {
Log.w(TAG, "updateInfo(): Fragment closed before thread ended work... or else");
@@ -247,48 +321,94 @@ public class VideoItemDetailFragment extends Fragment {
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
try {
StreamingService streamingService = ServiceList.getService(
getArguments().getInt(STREAMING_SERVICE));
extractorThread = new Thread(new ExtractorRunnable(
getArguments().getString(VIDEO_URL), streamingService.getExtractorClass(), this));
autoPlayEnabled = getArguments().getBoolean(AUTO_PLAY);
extractorThread.start();
} catch (Exception e) {
e.printStackTrace();
}
activity = (AppCompatActivity) getActivity();
showNextVideoItem = PreferenceManager.getDefaultSharedPreferences(getActivity())
.getBoolean(activity.getString(R.string.showNextVideo), true);
}
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
View rootView = inflater.inflate(R.layout.fragment_videoitem_detail, container, false);
actionBarHandler = new ActionBarHandler(activity);
actionBarHandler.setupNavMenu(activity);
if(onInvokeCreateOptionsMenuListener != null) {
onInvokeCreateOptionsMenuListener.createOptionsMenu();
}
return rootView;
}
@Override
public void onActivityCreated(Bundle savedInstanceBundle) {
super.onActivityCreated(savedInstanceBundle);
FloatingActionButton playVideoButton = (FloatingActionButton) getActivity().findViewById(R.id.playVideoButton);
FloatingActionButton playVideoButton =
(FloatingActionButton) getActivity().findViewById(R.id.playVideoButton);
if(PreferenceManager.getDefaultSharedPreferences(getActivity())
.getBoolean(getString(R.string.leftHandLayout), false) && checkIfLandscape()) {
RelativeLayout.LayoutParams oldLayout = (RelativeLayout.LayoutParams) playVideoButton.getLayoutParams();
RelativeLayout.LayoutParams layoutParams = new RelativeLayout.LayoutParams(
RelativeLayout.LayoutParams.WRAP_CONTENT,
RelativeLayout.LayoutParams.WRAP_CONTENT);
layoutParams.addRule(RelativeLayout.ALIGN_PARENT_BOTTOM);
layoutParams.addRule(RelativeLayout.ALIGN_PARENT_LEFT);
layoutParams.setMargins(oldLayout.leftMargin, oldLayout.topMargin, oldLayout.rightMargin, oldLayout.rightMargin);
playVideoButton.setLayoutParams(layoutParams);
}
// Sometimes when this fragment is not visible it still gets initiated
// then we must not try to access objects of this fragment.
// Otherwise the applications would crash.
if(playVideoButton != null) {
try {
StreamingService streamingService = ServiceList.getService(
getArguments().getInt(STREAMING_SERVICE));
extractorThread = new Thread(new ExtractorRunnable(
getArguments().getString(VIDEO_URL), streamingService.getExtractorInstance(), this));
playVideoButton.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
ActionBarHandler.getHandler().playVideo();
autoPlayEnabled = getArguments().getBoolean(AUTO_PLAY);
extractorThread.start();
} catch (Exception e) {
e.printStackTrace();
}
});
if (PreferenceManager.getDefaultSharedPreferences(getActivity())
.getBoolean(getString(R.string.leftHandLayout), false) && checkIfLandscape()) {
RelativeLayout.LayoutParams oldLayout =
(RelativeLayout.LayoutParams) playVideoButton.getLayoutParams();
RelativeLayout.LayoutParams layoutParams = new RelativeLayout.LayoutParams(
RelativeLayout.LayoutParams.WRAP_CONTENT,
RelativeLayout.LayoutParams.WRAP_CONTENT);
layoutParams.addRule(RelativeLayout.ALIGN_PARENT_BOTTOM);
layoutParams.addRule(RelativeLayout.ALIGN_PARENT_LEFT);
layoutParams.setMargins(oldLayout.leftMargin, oldLayout.topMargin,
oldLayout.rightMargin, oldLayout.bottomMargin);
playVideoButton.setLayoutParams(layoutParams);
}
playVideoButton.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
actionBarHandler.playVideo();
}
});
Button similarVideosButton = (Button) activity.findViewById(R.id.detailShowSimilarButton);
similarVideosButton.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
Intent intent = new Intent(activity, VideoItemListActivity.class);
intent.putExtra(VideoItemListActivity.VIDEO_INFO_ITEMS, currentVideoInfo.relatedVideos);
activity.startActivity(intent);
}
});
}
}
public Locale getPreferredLocale() {
SharedPreferences sp = PreferenceManager.getDefaultSharedPreferences(getContext());
String languageKey = getContext().getString(R.string.searchLanguage);
String languageCode = "en";//i know the following lines defaults languageCode to "en", but java is picky about uninitialised values
languageCode = sp.getString(languageKey, "en");
if(languageCode.length() == 2) {
return new Locale(languageCode);
}
else if(languageCode.contains("_")) {
String country = languageCode
.substring(languageCode.indexOf("_"), languageCode.length());
return new Locale(languageCode.substring(0, 2), country);
}
return Locale.getDefault();
}
public boolean checkIfLandscape() {
@@ -296,4 +416,18 @@ public class VideoItemDetailFragment extends Fragment {
getActivity().getWindowManager().getDefaultDisplay().getMetrics(displayMetrics);
return displayMetrics.heightPixels < displayMetrics.widthPixels;
}
@Override
public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
actionBarHandler.setupMenu(menu, inflater);
}
@Override
public boolean onOptionsItemSelected(MenuItem item) {
return actionBarHandler.onItemSelected(item);
}
public void setOnInvokeCreateOptionsMenuListener(OnInvokeCreateOptionsMenuListener listener) {
this.onInvokeCreateOptionsMenuListener = listener;
}
}

View File

@@ -2,23 +2,23 @@ package org.schabi.newpipe;
import android.content.Context;
import android.content.Intent;
import android.content.res.Configuration;
import android.os.Build;
import android.os.Bundle;
import android.os.Environment;
import android.preference.PreferenceManager;
import android.os.Parcel;
import android.os.Parcelable;
import android.support.v4.app.NavUtils;
import android.support.v7.app.AppCompatActivity;
import android.util.DisplayMetrics;
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.WindowManager;
import android.view.inputmethod.InputMethodManager;
import android.support.v7.widget.SearchView;
import android.widget.ImageView;
import java.util.ArrayList;
import java.util.Arrays;
/**
* Copyright (C) Christian Schabesberger 2015 <chris.schabesberger@mailbox.org>
* VideoItemListActivity.java is part of NewPipe.
@@ -41,13 +41,25 @@ 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 = SEARCH_MODE;
private int currentStreamingServiceId = -1;
private String searchQuery = "";
private VideoItemListFragment listFragment;
private VideoItemDetailFragment videoFragment = null;
Menu menu = null;
public class SearchVideoQueryListener implements SearchView.OnQueryTextListener {
@@ -68,7 +80,6 @@ public class VideoItemListActivity extends AppCompatActivity
// 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();
hideWatermark();
} catch(Exception e) {
e.printStackTrace();
}
@@ -82,13 +93,6 @@ public class VideoItemListActivity extends AppCompatActivity
}
private void hideWatermark() {
ImageView waterMark = (ImageView) findViewById(R.id.list_view_watermark);
if(waterMark != null) {
waterMark.setVisibility(View.GONE);
}
}
/**
* Whether or not the activity is in two-pane mode, i.e. running on a tablet
* device.
@@ -100,21 +104,33 @@ public class VideoItemListActivity extends AppCompatActivity
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_videoitem_list);
listFragment = (VideoItemListFragment)
getSupportFragmentManager().findFragmentById(R.id.videoitem_list);
//-------- remove this line when multiservice support is implemented ----------
//------ todo: remove this line when multiservice support is implemented ------
currentStreamingServiceId = ServiceList.getIdOfService("Youtube");
//-----------------------------------------------------------------------------
VideoItemListFragment listFragment = (VideoItemListFragment) getSupportFragmentManager()
listFragment = (VideoItemListFragment) getSupportFragmentManager()
.findFragmentById(R.id.videoitem_list);
listFragment.setStreamingService(ServiceList.getService(currentStreamingServiceId));
if(savedInstanceState != null) {
Bundle arguments = getIntent().getExtras();
if(arguments != null) {
//Parcelable[] p = arguments.getParcelableArray(VIDEO_INFO_ITEMS);
ArrayList<VideoInfoItem> p = arguments.getParcelableArrayList(VIDEO_INFO_ITEMS);
if(p != null) {
mode = PRESENT_VIDEOS_MODE;
getSupportActionBar().setDisplayHomeAsUpEnabled(true);
listFragment.present(p);
}
}
if(savedInstanceState != null
&& mode != PRESENT_VIDEOS_MODE) {
searchQuery = savedInstanceState.getString(QUERY);
currentStreamingServiceId = savedInstanceState.getInt(STREAMING_SERVICE);
if(!searchQuery.isEmpty()) {
hideWatermark();
listFragment.search(searchQuery);
}
}
@@ -134,19 +150,18 @@ public class VideoItemListActivity extends AppCompatActivity
.setActivateOnItemClick(true);
SearchView searchView = (SearchView)findViewById(R.id.searchViewTablet);
// 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);
searchView.setOnQueryTextListener(new SearchVideoQueryListener());
ActionBarHandler.getHandler().setupNavMenu(this);
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);
searchView.setOnQueryTextListener(new SearchVideoQueryListener());
} else {
searchView.setVisibility(View.GONE);
}
}
SettingsActivity.initSettings(this);
// TODO: If exposing deep links into your app, handle intents here.
}
/**
@@ -168,10 +183,17 @@ public class VideoItemListActivity extends AppCompatActivity
arguments.putString(VideoItemDetailFragment.ARG_ITEM_ID, id);
arguments.putString(VideoItemDetailFragment.VIDEO_URL, webpage_url);
arguments.putInt(VideoItemDetailFragment.STREAMING_SERVICE, currentStreamingServiceId);
VideoItemDetailFragment fragment = new VideoItemDetailFragment();
fragment.setArguments(arguments);
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, fragment)
.replace(R.id.videoitem_detail_container, videoFragment)
.commit();
} else {
// In single-pane mode, simply start the detail activity
@@ -185,10 +207,12 @@ public class VideoItemListActivity extends AppCompatActivity
}
public boolean onCreatePanelMenu(int featured, Menu menu) {
super.onCreatePanelMenu(featured, menu);
if(findViewById(R.id.videoitem_detail_container) == null) {
MenuInflater inflater = getMenuInflater();
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();
@@ -196,9 +220,10 @@ public class VideoItemListActivity extends AppCompatActivity
searchView.setOnQueryTextListener(
new SearchVideoQueryListener());
} else if (videoFragment != null){
videoFragment.onCreateOptionsMenu(menu, inflater);
} else {
MenuInflater inflater = getMenuInflater();
ActionBarHandler.getHandler().setupMenu(menu, inflater, this);
inflater.inflate(R.menu.videoitem_two_pannel, menu);
}
return true;
@@ -207,14 +232,23 @@ public class VideoItemListActivity extends AppCompatActivity
@Override
public boolean onOptionsItemSelected(MenuItem item) {
int id = item.getItemId();
if(id == R.id.action_settings) {
Intent intent = new Intent(this, SettingsActivity.class);
startActivity(intent);
} else {
ActionBarHandler.getHandler().onItemSelected(item, this);
return super.onOptionsItemSelected(item);
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;
}
default:
return videoFragment.onOptionsItemSelected(item) ||
super.onOptionsItemSelected(item);
}
return true;
}
@Override

View File

@@ -1,10 +1,12 @@
package org.schabi.newpipe;
import android.app.Activity;
import android.content.Context;
import android.content.SharedPreferences;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
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.View;
@@ -13,6 +15,7 @@ import android.widget.ListView;
import android.widget.Toast;
import java.net.URL;
import java.util.Arrays;
import java.util.List;
import java.util.Vector;
@@ -42,6 +45,11 @@ public class VideoItemListFragment extends ListFragment {
private StreamingService streamingService = null;
private VideoListAdapter videoListAdapter;
// activity modes
private static final int SEARCH_MODE = 0;
private static final int PRESENT_VIDEOS_MODE = 1;
private int mode = SEARCH_MODE;
private String query = "";
private int lastPage = 0;
@@ -51,29 +59,30 @@ public class VideoItemListFragment extends ListFragment {
private LoadThumbsRunnable loadThumbsRunnable = null;
// used to track down if results posted by threads ar still valid
private int currentRequestId = -1;
private ListView list;
private class ResultRunnable implements Runnable {
private SearchEngine.Result result;
private int reuqestId;
private int requestId;
public ResultRunnable(SearchEngine.Result result, int requestId) {
this.result = result;
this.reuqestId = requestId;
this.requestId = requestId;
}
@Override
public void run() {
updateListOnResult(result, reuqestId);
updateListOnResult(result, requestId);
}
}
private class SearchRunnable implements Runnable {
private Class engineClass = null;
private SearchEngine engine;
private String query;
private int page;
Handler h = new Handler();
private volatile boolean run = true;
private int requestId;
public SearchRunnable(Class engineClass, String query, int page, int requestId) {
this.engineClass = engineClass;
public SearchRunnable(SearchEngine engine, String query, int page, int requestId) {
this.engine = engine;
this.query = query;
this.page = page;
this.requestId = requestId;
@@ -83,15 +92,12 @@ public class VideoItemListFragment extends ListFragment {
}
@Override
public void run() {
SearchEngine engine = null;
try {
engine = (SearchEngine) engineClass.newInstance();
} catch(Exception e) {
e.printStackTrace();
return;
}
try {
SearchEngine.Result result = engine.search(query, page);
SharedPreferences sp = PreferenceManager.getDefaultSharedPreferences(getContext());
String searchLanguageKey = getContext().getString(R.string.searchLanguage);
String searchLanguage = sp.getString(searchLanguageKey, "en");
SearchEngine.Result result = engine.search(query, page, searchLanguage);
Log.i(TAG, "language code passed:\""+searchLanguage+"\"");
if(run) {
h.post(new ResultRunnable(result, requestId));
}
@@ -162,7 +168,16 @@ public class VideoItemListFragment extends ListFragment {
}
}
public void present(List<VideoInfoItem> 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();
@@ -180,7 +195,8 @@ public class VideoItemListFragment extends ListFragment {
private void startSearch(String query, int page) {
currentRequestId++;
terminateThreads();
searchRunnable = new SearchRunnable(streamingService.getSearchEngineClass(), query, page, currentRequestId);
searchRunnable = new SearchRunnable(streamingService.getSearchEngineInstance(),
query, page, currentRequestId);
searchThread = new Thread(searchRunnable);
searchThread.start();
}
@@ -204,7 +220,7 @@ public class VideoItemListFragment extends ListFragment {
}
}
private void updateList(Vector<VideoInfoItem> list) {
private void updateList(List<VideoInfoItem> list) {
try {
videoListAdapter.addVideoList(list);
terminateThreads();
@@ -213,7 +229,7 @@ public class VideoItemListFragment extends ListFragment {
loadThumbsThread = new Thread(loadThumbsRunnable);
loadThumbsThread.start();
} catch(java.lang.IllegalStateException e) {
Log.w(TAG, "Trying to set value while activity is not existing anymore.");
Log.w(TAG, "Trying to set value while activity doesn't exist anymore.");
} catch(Exception e) {
e.printStackTrace();
}
@@ -230,15 +246,11 @@ public class VideoItemListFragment extends ListFragment {
}
if(searchThread != null) {
searchRunnable.terminate();
// No need to join, since we don't realy terminate the thread. We just demand
// 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.
}
}
void displayList() {
}
/**
* The serialization (saved instance state) Bundle key representing the
* activated item position. Only used on tablets.
@@ -272,6 +284,7 @@ public class VideoItemListFragment extends ListFragment {
@Override
public void onViewCreated(View view, Bundle savedInstanceState) {
super.onViewCreated(view, savedInstanceState);
list = getListView();
videoListAdapter = new VideoListAdapter(getActivity(), this);
setListAdapter(videoListAdapter);
@@ -283,8 +296,6 @@ public class VideoItemListFragment extends ListFragment {
}
getListView().setOnScrollListener(new AbsListView.OnScrollListener() {
private static final float OVERSCROLL_THRESHOLD_IN_PIXELS = 100;
private float downY;
long lastScrollDate = 0;
@Override
@@ -293,8 +304,8 @@ public class VideoItemListFragment extends ListFragment {
@Override
public void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount, int totalItemCount) {
ListView list = getListView();
if (list.getChildAt(0) != null
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();
@@ -309,15 +320,15 @@ public class VideoItemListFragment extends ListFragment {
}
@Override
public void onAttach(Activity activity) {
super.onAttach(activity);
public void onAttach(Context context) {
super.onAttach(context);
// Activities containing this fragment must implement its callbacks.
if (!(activity instanceof Callbacks)) {
if (!(context instanceof Callbacks)) {
throw new IllegalStateException("Activity must implement fragment's callbacks.");
}
mCallbacks = (Callbacks) activity;
mCallbacks = (Callbacks) context;
}
@Override

View File

@@ -2,18 +2,18 @@ package org.schabi.newpipe;
import android.content.Context;
import android.graphics.Bitmap;
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.ImageView;
import android.widget.ListView;
import android.widget.TextView;
import java.util.List;
import java.util.Vector;
/**
* Created by the-scrabi on 11.08.15.
* Created by Christian Schabesberger on 11.08.15.
*
* Copyright (C) Christian Schabesberger 2015 <chris.schabesberger@mailbox.org>
* VideoListAdapter.java is part of NewPipe.
@@ -33,21 +33,23 @@ import java.util.Vector;
*/
public class VideoListAdapter extends BaseAdapter {
private static final String TAG = VideoListAdapter.class.toString();
private LayoutInflater inflater;
private Context context;
private VideoInfoItemViewCreator viewCreator;
private Vector<VideoInfoItem> videoList = new Vector<>();
private Vector<Boolean> downloadedThumbnailList = new Vector<>();
VideoItemListFragment videoListFragment;
ListView listView;
public VideoListAdapter(Context context, VideoItemListFragment videoListFragment) {
inflater = LayoutInflater.from(context);
viewCreator = new VideoInfoItemViewCreator(LayoutInflater.from(context));
this.videoListFragment = videoListFragment;
this.listView = videoListFragment.getListView();
this.context = context;
}
public void addVideoList(Vector<VideoInfoItem> videos) {
public void addVideoList(List<VideoInfoItem> videos) {
videoList.addAll(videos);
for(int i = 0; i < videos.size(); i++) {
downloadedThumbnailList.add(false);
@@ -96,42 +98,14 @@ public class VideoListAdapter extends BaseAdapter {
@Override
public View getView(int position, View convertView, ViewGroup parent) {
ViewHolder 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);
convertView.setTag(holder);
} else {
holder = (ViewHolder) convertView.getTag();
}
final Context context = parent.getContext();
if(videoList.get(position).thumbnail == null) {
holder.itemThumbnailView.setImageResource(R.drawable.dummi_thumbnail);
} else {
holder.itemThumbnailView.setImageBitmap(videoList.get(position).thumbnail);
}
holder.itemVideoTitleView.setText(videoList.get(position).title);
holder.itemUploaderView.setText(videoList.get(position).uploader);
holder.itemDurationView.setText(videoList.get(position).duration);
holder.itemUploadDateView.setText(videoList.get(position).upload_date);
convertView = viewCreator.getViewByVideoInfoItem(convertView, parent, videoList.get(position));
if(listView.isItemChecked(position)) {
convertView.setBackgroundColor(context.getResources().getColor(R.color.primaryColorYoutube));
convertView.setBackgroundColor(ContextCompat.getColor(context,R.color.primaryColorYoutube));
} else {
convertView.setBackgroundColor(0);
}
return convertView;
}
private class ViewHolder {
public ImageView itemThumbnailView;
public TextView itemVideoTitleView, itemUploaderView, itemDurationView, itemUploadDateView;
}
}

View File

@@ -1,31 +1,32 @@
package org.schabi.newpipe.youtube;
import org.jsoup.nodes.Element;
import org.schabi.newpipe.Downloader;
import org.schabi.newpipe.Extractor;
import org.schabi.newpipe.VideoInfo;
import android.util.Log;
import android.util.Xml;
import java.io.StringReader;
import java.net.URI;
import java.util.HashMap;
import java.util.Map;
import java.util.Vector;
import java.util.regex.Pattern;
import java.util.regex.Matcher;
import org.json.JSONObject;
import org.jsoup.Jsoup;
import org.jsoup.nodes.Document;
import org.jsoup.nodes.Element;
import org.jsoup.parser.Parser;
import org.mozilla.javascript.Context;
import org.mozilla.javascript.Function;
import org.mozilla.javascript.ScriptableObject;
import org.schabi.newpipe.Downloader;
import org.schabi.newpipe.Extractor;
import org.schabi.newpipe.MediaFormat;
import org.schabi.newpipe.VideoInfo;
import org.schabi.newpipe.VideoInfoItem;
import org.xmlpull.v1.XmlPullParser;
import java.io.StringReader;
import java.net.URI;
import java.net.URLDecoder;
import java.util.HashMap;
import java.util.Map;
import java.util.Vector;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
/**
* Created by Christian Schabesberger on 06.08.15.
*
@@ -48,27 +49,25 @@ import org.xmlpull.v1.XmlPullParser;
public class YoutubeExtractor implements Extractor {
private static final String TAG = YoutubeExtractor.class.toString();
// These lists only contain itag formats that are supported by the common Android Video player.
// How ever if you are heading for a list showing all itag formats lock at
// How ever if you are heading for a list showing all itag formats look at
// https://github.com/rg3/youtube-dl/issues/1687
public static int resolveFormat(int itag) {
switch(itag) {
// video
case 17: return VideoInfo.I_3GPP;
case 18: return VideoInfo.I_MPEG_4;
case 22: return VideoInfo.I_MPEG_4;
case 36: return VideoInfo.I_3GPP;
case 37: return VideoInfo.I_MPEG_4;
case 38: return VideoInfo.I_MPEG_4;
case 43: return VideoInfo.I_WEBM;
case 44: return VideoInfo.I_WEBM;
case 45: return VideoInfo.I_WEBM;
case 46: return VideoInfo.I_WEBM;
case 17: return MediaFormat.v3GPP.id;
case 18: return MediaFormat.MPEG_4.id;
case 22: return MediaFormat.MPEG_4.id;
case 36: return MediaFormat.v3GPP.id;
case 37: return MediaFormat.MPEG_4.id;
case 38: return MediaFormat.MPEG_4.id;
case 43: return MediaFormat.WEBM.id;
case 44: return MediaFormat.WEBM.id;
case 45: return MediaFormat.WEBM.id;
case 46: return MediaFormat.WEBM.id;
default:
//Log.i(TAG, "Itag " + Integer.toString(itag) + " not known or not supported.");
return -1;
@@ -93,38 +92,68 @@ public class YoutubeExtractor implements Extractor {
}
}
private String decryptoinCode = "";
// static values
private static final String DECRYPTION_FUNC_NAME="decrypt";
// cached values
private static volatile String decryptionCode = "";
public void initService(String site) {
// The Youtube service needs to be initialized by downloading the
// js-Youtube-player. This is done in order to get the algorithm
// for decrypting cryptic signatures inside certain stream urls.
// Star Wars Kid is used as a dummy video, in order to download the youtube player.
//String site = Downloader.download("https://www.youtube.com/watch?v=HPPj6viIBmU");
//-------------------------------------
// extracting form player args
//-------------------------------------
try {
String jsonString = matchGroup1("ytplayer.config\\s*=\\s*(\\{.*?\\});", site);
JSONObject jsonObj = new JSONObject(jsonString);
//----------------------------------
// load and parse description code
//----------------------------------
if (decryptionCode.isEmpty()) {
JSONObject ytAssets = jsonObj.getJSONObject("assets");
String playerUrl = ytAssets.getString("js");
if (playerUrl.startsWith("//")) {
playerUrl = "https:" + playerUrl;
}
decryptionCode = loadDecryptionCode(playerUrl);
}
} catch (Exception e){
Log.d(TAG, "Could not initialize the extractor of the Youtube service.");
e.printStackTrace();
}
}
@Override
public String getVideoId(String videoUrl) {
try {
URI uri = new URI(videoUrl);
if(uri.getHost().contains("youtube")) {
String query = uri.getQuery();
String queryElements[] = query.split("&");
Map<String, String> queryArguments = new HashMap<>();
for (String e : queryElements) {
String[] s = e.split("=");
queryArguments.put(s[0], s[1]);
}
return queryArguments.get("v");
} else if(uri.getHost().contains("youtu.be")) {
// uri.getRawPath() does somehow not return the last character.
// so we do a workaround instead.
//return uri.getRawPath();
String url[] = videoUrl.split("/");
return url[url.length-1];
} else {
Log.e(TAG, "Error could not parse url: " + videoUrl);
String id = "";
Pattern pat;
}
} catch(Exception e) {
if(videoUrl.contains("youtube")) {
pat = Pattern.compile("youtube\\.com/watch\\?v=([\\-a-zA-Z0-9_]{11})");
}
else if(videoUrl.contains("youtu.be")) {
pat = Pattern.compile("youtu\\.be/([a-zA-Z0-9_-]{11})");
}
else {
Log.e(TAG, "Error could not parse url: " + videoUrl);
e.printStackTrace();
return "";
}
return null;
Matcher mat = pat.matcher(videoUrl);
boolean foundMatch = mat.find();
if(foundMatch){
id = mat.group(1);
Log.i(TAG, "string \""+videoUrl+"\" matches!");
}
Log.i(TAG, "string \""+videoUrl+"\" does not match.");
return id;
}
@Override
@@ -139,35 +168,24 @@ public class YoutubeExtractor implements Extractor {
Document doc = Jsoup.parse(site, siteUrl);
try {
Pattern p = Pattern.compile("v=([0-9a-zA-Z]*)");
Matcher m = p.matcher(siteUrl);
m.find();
videoInfo.id = m.group(1);
} catch (Exception e) {
e.printStackTrace();
}
videoInfo.id = matchGroup1("v=([0-9a-zA-Z_-]{11})", siteUrl);
videoInfo.age_limit = 0;
videoInfo.webpage_url = siteUrl;
initService(site);
//-------------------------------------
// extracting form player args
//-------------------------------------
JSONObject playerArgs = null;
JSONObject ytAssets = null;
String dashManifest = "";
{
Pattern p = Pattern.compile("ytplayer.config\\s*=\\s*(\\{.*?\\});");
Matcher m = p.matcher(site);
m.find();
try {
playerArgs = (new JSONObject(m.group(1)))
.getJSONObject("args");
ytAssets = (new JSONObject(m.group(1)))
.getJSONObject("assets");
}catch (Exception e) {
String jsonString = matchGroup1("ytplayer.config\\s*=\\s*(\\{.*?\\});", site);
JSONObject jsonObj = new JSONObject(jsonString);
playerArgs = jsonObj.getJSONObject("args");
}
catch (Exception e) {
e.printStackTrace();
// If we fail in this part the video is most likely not available.
// Determining why is done later.
@@ -175,26 +193,31 @@ public class YoutubeExtractor implements Extractor {
}
}
//-----------------------
// load and extract audio
//-----------------------
try {
String dashManifest = playerArgs.getString("dashmpd");
videoInfo.audioStreams = parseDashManifest(dashManifest, decryptionCode);
} catch (NullPointerException e) {
Log.e(TAG, "Could not find \"dashmpd\" upon the player args (maybe no dash manifest available).");
} catch (Exception e) {
e.printStackTrace();
}
try {
//--------------------------------------------
// extract general information about the video
//--------------------------------------------
videoInfo.uploader = playerArgs.getString("author");
videoInfo.title = playerArgs.getString("title");
//first attempt gating a small image version
//first attempt getting a small image version
//in the html extracting part we try to get a thumbnail with a higher resolution
videoInfo.thumbnail_url = playerArgs.getString("thumbnail_url");
videoInfo.duration = playerArgs.getInt("length_seconds");
videoInfo.average_rating = playerArgs.getString("avg_rating");
// View Count will be extracted from html
dashManifest = playerArgs.getString("dashmpd");
String playerUrl = ytAssets.getString("js");
if(playerUrl.startsWith("//")) {
playerUrl = "https:" + playerUrl;
}
if(decryptoinCode.isEmpty()) {
decryptoinCode = loadDecryptioinCode(playerUrl);
}
// extract audio
videoInfo.audioStreams = parseDashManifest(dashManifest, decryptoinCode);
//------------------------------------
// extract video stream url
@@ -209,14 +232,11 @@ public class YoutubeExtractor implements Extractor {
}
int itag = Integer.parseInt(tags.get("itag"));
String streamUrl = terrible_unescape_workaround_fuck(tags.get("url"));
String streamUrl = URLDecoder.decode(tags.get("url"), "UTF-8");
// if video has a signature: decrypt it and add it to the url
if(tags.get("s") != null) {
if(decryptoinCode.isEmpty()) {
decryptoinCode = loadDecryptioinCode(playerUrl);
}
streamUrl = streamUrl + "&signature=" + decryptSignature(tags.get("s"), decryptoinCode);
streamUrl = streamUrl + "&signature=" + decryptSignature(tags.get("s"), decryptionCode);
}
if(resolveFormat(itag) != -1) {
@@ -226,18 +246,16 @@ public class YoutubeExtractor implements Extractor {
resolveResolutionString(itag)));
}
}
videoInfo.videoStreams = new VideoInfo.VideoStream[videoStreams.size()];
for(int i = 0; i < videoStreams.size(); i++) {
videoInfo.videoStreams[i] = videoStreams.get(i);
}
videoInfo.videoStreams =
videoStreams.toArray(new VideoInfo.VideoStream[videoStreams.size()]);
} catch (Exception e) {
e.printStackTrace();
}
//-------------------------------
// extrating from html page
//-------------------------------
//---------------------------------------
// extracting information from html page
//---------------------------------------
// Determine what went wrong when the Video is not available
@@ -252,41 +270,37 @@ public class YoutubeExtractor implements Extractor {
videoInfo.thumbnail_url = doc.select("link[itemprop=\"thumbnailUrl\"]").first()
.attr("abs:href");
} catch(Exception e) {
Log.i(TAG, "Could not find high res Thumbnail. Use low res instead");
Log.i(TAG, "Could not find high res Thumbnail. Using low res instead");
}
// upload date
videoInfo.upload_date = doc.select("strong[class=\"watch-time-text\"").first()
.text();
// Try to only use date not the text around it
try {
Pattern p = Pattern.compile("([0-9.]*$)");
Matcher m = p.matcher(videoInfo.upload_date);
m.find();
videoInfo.upload_date = m.group(1);
} catch (Exception e) {
e.printStackTrace();
}
videoInfo.upload_date = doc.select("meta[itemprop=datePublished]").attr("content");
//TODO: Format date locale-specifically
// description
videoInfo.description = doc.select("p[id=\"eow-description\"]").first()
.html();
videoInfo.description = doc.select("p[id=\"eow-description\"]").first().html();
String likesString = "";
String dislikesString = "";
try {
// likes
videoInfo.like_count = doc.select("span[class=\"like-button-renderer \"]").first()
.getAllElements().select("button")
.select("span").get(0).text();
likesString = doc.select("button.like-button-renderer-like-button").first()
.select("span.yt-uix-button-content").first().text();
videoInfo.like_count = Integer.parseInt(likesString.replaceAll("[^\\d]", ""));
// dislikes
videoInfo.dislike_count = doc.select("span[class=\"like-button-renderer \"]").first()
.getAllElements().select("button")
.select("span").get(2).text();
dislikesString = doc.select("button.like-button-renderer-dislike-button").first()
.select("span.yt-uix-button-content").first().text();
videoInfo.dislike_count = Integer.parseInt(dislikesString.replaceAll("[^\\d]", ""));
} catch(NumberFormatException nfe) {
Log.e(TAG, "failed to parse likesString \""+likesString+"\" and dislikesString \""+
dislikesString+"\" as integers");
} catch(Exception e) {
// if it fails we know that the video does not offer dislikes.
videoInfo.like_count = "0";
videoInfo.dislike_count = "0";
e.printStackTrace();
videoInfo.like_count = 0;
videoInfo.dislike_count = 0;
}
// uploader thumbnail
@@ -294,43 +308,31 @@ public class YoutubeExtractor implements Extractor {
.select("img").first()
.attr("abs:data-thumb");
// view count
videoInfo.view_count = doc.select("div[class=\"watch-view-count\"]").first().text();
/* todo finish this code
// view count TODO: locale-specific formatting
String viewCountString = doc.select("meta[itemprop=interactionCount]").attr("content");
videoInfo.view_count = Integer.parseInt(viewCountString);
// next video
videoInfo.nextVideo = extractVideoInfoItem(doc.select("div[class=\"watch-sidebar-section\"]").first()
.select("li").first());
int i = 0;
// related videos
Vector<VideoInfoItem> relatedVideos = new Vector<>();
for(Element li : doc.select("ul[id=\"watch-related\"]").first().children()) {
// first check if we have a playlist. If so leave them out
if(li.select("a[class*=\"content-link\"]").first() != null) {
//videoInfo.relatedVideos.add(extractVideoInfoItem(li));
//i++;
//Log.d(TAG, Integer.toString(i));
relatedVideos.add(extractVideoInfoItem(li));
}
}
*/
videoInfo.relatedVideos = relatedVideos.toArray(new VideoInfoItem[relatedVideos.size()]);
return videoInfo;
}
private VideoInfo.AudioStream[] parseDashManifest(String dashManifest, String decryptoinCode) {
if(!dashManifest.contains("/signature/")) {
String encryptedSig = "";
String decryptedSig = "";
try {
Pattern p = Pattern.compile("/s/([a-fA-F0-9\\.]+)");
Matcher m = p.matcher(dashManifest);
m.find();
encryptedSig = m.group(1);
} catch (Exception e) {
e.printStackTrace();
}
String encryptedSig = matchGroup1("/s/([a-fA-F0-9\\.]+)", dashManifest);
String decryptedSig;
decryptedSig = decryptSignature(encryptedSig, decryptoinCode);
dashManifest = dashManifest.replace("/s/" + encryptedSig, "/signature/" + decryptedSig);
}
@@ -365,13 +367,13 @@ public class YoutubeExtractor implements Extractor {
if(currentTagIsBaseUrl &&
(currentMimeType.contains("audio"))) {
int format = -1;
if(currentMimeType.equals(VideoInfo.M_WEBMA)) {
format = VideoInfo.I_WEBMA;
} else if(currentMimeType.equals(VideoInfo.M_M4A)) {
format = VideoInfo.I_M4A;
if(currentMimeType.equals(MediaFormat.WEBMA.mimeType)) {
format = MediaFormat.WEBMA.id;
} else if(currentMimeType.equals(MediaFormat.M4A.mimeType)) {
format = MediaFormat.M4A.id;
}
audioStreams.add(new VideoInfo.AudioStream(parser.getText(),
format, currentBandwidth, currentSamplingRate));
format, currentBandwidth, currentSamplingRate));
}
case XmlPullParser.END_TAG:
if(tagName.equals("AdaptationSet")) {
@@ -387,11 +389,7 @@ public class YoutubeExtractor implements Extractor {
} catch(Exception e) {
e.printStackTrace();
}
VideoInfo.AudioStream[] output = new VideoInfo.AudioStream[audioStreams.size()];
for(int i = 0; i < output.length; i++) {
output[i] = audioStreams.get(i);
}
return output;
return audioStreams.toArray(new VideoInfo.AudioStream[audioStreams.size()]);
}
private VideoInfoItem extractVideoInfoItem(Element li) {
@@ -399,18 +397,15 @@ public class YoutubeExtractor implements Extractor {
info.webpage_url = li.select("a[class*=\"content-link\"]").first()
.attr("abs:href");
try {
Pattern p = Pattern.compile("v=([0-9a-zA-Z-]*)");
Matcher m = p.matcher(info.webpage_url);
m.find();
info.id=m.group(1);
info.id = matchGroup1("v=([0-9a-zA-Z-]*)", info.webpage_url);
} catch (Exception e) {
e.printStackTrace();
}
info.title = li.select("span[class=\"title\"]").first()
.text();
//todo: check NullPointerException causing
info.title = li.select("span[class=\"title\"]").first().text();
info.view_count = li.select("span[class*=\"view-count\"]").first().text();
info.uploader = li.select("span[class=\"g-hovercard\"]").first().text();
info.duration = li.select("span[class=\"video-time\"]").first().text();
Element img = li.select("img").first();
@@ -421,24 +416,13 @@ public class YoutubeExtractor implements Extractor {
if(info.thumbnail_url.contains(".gif")) {
info.thumbnail_url = img.attr("data-thumb");
}
if(info.thumbnail_url.startsWith("//")) {
info.thumbnail_url = "https:" + info.thumbnail_url;
}
return info;
}
private String terrible_unescape_workaround_fuck(String shit) {
String[] splitAtEscape = shit.split("%");
String retval = "";
retval += splitAtEscape[0];
for(int i = 1; i < splitAtEscape.length; i++) {
String escNum = splitAtEscape[i].substring(0, 2);
char c = (char) Integer.parseInt(escNum,16);
retval += c;
retval += splitAtEscape[i].substring(2);
}
return retval;
}
private String loadDecryptioinCode(String playerUrl) {
private String loadDecryptionCode(String playerUrl) {
String playerCode = Downloader.download(playerUrl);
String decryptionFuncName = "";
String decryptionFunc = "";
@@ -448,27 +432,16 @@ public class YoutubeExtractor implements Extractor {
String decryptionCode;
try {
Pattern p = Pattern.compile("\\.sig\\|\\|([a-zA-Z0-9$]+)\\(");
Matcher m = p.matcher(playerCode);
m.find();
decryptionFuncName = m.group(1);
decryptionFuncName = matchGroup1("\\.sig\\|\\|([a-zA-Z0-9$]+)\\(", playerCode);
String functionPattern = "(function " + decryptionFuncName.replace("$", "\\$") + "\\([a-zA-Z0-9_]*\\)\\{.+?\\})";
p = Pattern.compile(functionPattern);
m = p.matcher(playerCode);
m.find();
decryptionFunc = m.group(1);
String functionPattern = "(var "+ decryptionFuncName.replace("$", "\\$") +"=function\\([a-zA-Z0-9_]*\\)\\{.+?\\})";
decryptionFunc = matchGroup1(functionPattern, playerCode);
decryptionFunc += ";";
p = Pattern.compile(";([A-Za-z0-9_\\$]{2})\\...\\(");
m = p.matcher(decryptionFunc);
m.find();
helperObjectName = m.group(1);
helperObjectName = matchGroup1(";([A-Za-z0-9_\\$]{2})\\...\\(", decryptionFunc);
String helperPattern = "(var " + helperObjectName.replace("$", "\\$") + "=\\{.+?\\}\\};)function";
p = Pattern.compile(helperPattern);
m = p.matcher(playerCode);
m.find();
helperObject = m.group(1);
String helperPattern = "(var " + helperObjectName.replace("$", "\\$") + "=\\{.+?\\}\\};)";
helperObject = matchGroup1(helperPattern, playerCode);
} catch (Exception e) {
e.printStackTrace();
@@ -480,13 +453,13 @@ public class YoutubeExtractor implements Extractor {
return decryptionCode;
}
private String decryptSignature(String encryptedSig, String decryptoinCode) {
private String decryptSignature(String encryptedSig, String decryptionCode) {
Context context = Context.enter();
context.setOptimizationLevel(-1);
Object result = null;
try {
ScriptableObject scope = context.initStandardObjects();
context.evaluateString(scope, decryptoinCode, "decryptionCode", 1, null);
context.evaluateString(scope, decryptionCode, "decryptionCode", 1, null);
Function decryptionFunc = (Function) scope.get("decrypt", scope);
result = decryptionFunc.call(context, scope, scope, new Object[]{encryptedSig});
} catch (Exception e) {
@@ -495,4 +468,19 @@ public class YoutubeExtractor implements Extractor {
Context.exit();
return result.toString();
}
private String matchGroup1(String pattern, String input) {
Pattern pat = Pattern.compile(pattern);
Matcher mat = pat.matcher(input);
boolean foundMatch = mat.find();
if(foundMatch){
return mat.group(1);
}
else {
Log.e(TAG, "failed to find pattern \""+pattern+"\" inside of \""+input+"\"");
new Exception("failed to find pattern \""+pattern+"\"").printStackTrace();
return "";
}
}
}

View File

@@ -1,18 +1,28 @@
package org.schabi.newpipe.youtube;
import org.schabi.newpipe.Downloader;
import org.schabi.newpipe.SearchEngine;
import org.schabi.newpipe.VideoInfoItem;
import android.net.Uri;
import android.util.Log;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import org.jsoup.Jsoup;
import org.jsoup.nodes.Document;
import org.jsoup.nodes.Element;
import org.schabi.newpipe.Downloader;
import org.schabi.newpipe.SearchEngine;
import org.schabi.newpipe.VideoInfoItem;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
import org.xml.sax.InputSource;
import org.xml.sax.SAXException;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.util.ArrayList;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;
/**
* Created by Christian Schabesberger on 09.08.15.
@@ -39,7 +49,8 @@ public class YoutubeSearchEngine implements SearchEngine {
private static final String TAG = YoutubeSearchEngine.class.toString();
@Override
public Result search(String query, int page) {
public Result search(String query, int page, String languageCode) {
//String contentCountry = PreferenceManager.getDefaultSharedPreferences(this).getString(getString(R.string., "");
Uri.Builder builder = new Uri.Builder();
builder.scheme("https")
.authority("www.youtube.com")
@@ -47,9 +58,19 @@ public class YoutubeSearchEngine implements SearchEngine {
.appendQueryParameter("search_query", query)
.appendQueryParameter("page", Integer.toString(page))
.appendQueryParameter("filters", "video");
String url = builder.build().toString();
String site = Downloader.download(url);
String site;
String url = builder.build().toString();
//if we've been passed a valid language code, append it to the URL
if(languageCode.length() > 0) {
//assert Pattern.matches("[a-z]{2}(-([A-Z]{2}|[0-9]{1,3}))?", languageCode);
site = Downloader.download(url, languageCode);
}
else {
site = Downloader.download(url);
}
Document doc = Jsoup.parse(site, url);
Result result = new Result();
Element list = doc.select("ol[class=\"item-section\"]").first();
@@ -58,14 +79,14 @@ public class YoutubeSearchEngine implements SearchEngine {
int i = 0;
for(Element item : list.children()) {
i++;
/* First we need to determine witch kind of item we are working with.
Youtube depicts fife different kinds if items at its search result page. These are
regular videos, playlists, channels, two types of video suggestions, and a no video
found item. Since we only want videos, we net to filter out all the others.
/* First we need to determine which kind of item we are working with.
Youtube depicts five different kinds of items on its search result page. These are
regular videos, playlists, channels, two types of video suggestions, and a "no video
found" item. Since we only want videos, we need to filter out all the others.
An example for this can be seen here:
https://www.youtube.com/results?search_query=asdf&page=1
We already applied a filter to the url, so we don't need to care about channels, and
We already applied a filter to the url, so we don't need to care about channels and
playlists now.
*/
@@ -103,9 +124,9 @@ public class YoutubeSearchEngine implements SearchEngine {
Element te = item.select("div[class=\"yt-thumb video-thumb\"]").first()
.select("img").first();
resultItem.thumbnail_url = te.attr("abs:src");
// Sometimes youtube sends links to gif files witch somehow seam to not exist
// 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 caught such an item.
// to use that if we've caught such an item.
if(resultItem.thumbnail_url.contains(".gif")) {
resultItem.thumbnail_url = te.attr("abs:data-thumb");
}
@@ -114,7 +135,55 @@ public class YoutubeSearchEngine implements SearchEngine {
Log.e(TAG, "GREAT FUCKING ERROR");
}
}
return result;
}
@Override
public ArrayList<String> suggestionList(String query) {
ArrayList<String> suggestions = new ArrayList<>();
Uri.Builder builder = new Uri.Builder();
builder.scheme("https")
.authority("suggestqueries.google.com")
.appendPath("complete")
.appendPath("search")
.appendQueryParameter("client", "")
.appendQueryParameter("output", "toolbar")
.appendQueryParameter("ds", "yt")
.appendQueryParameter("q", query);
String url = builder.build().toString();
String response = Downloader.download(url);
//TODO: Parse xml data using Jsoup not done
DocumentBuilderFactory dbFactory = DocumentBuilderFactory.newInstance();
DocumentBuilder dBuilder;
org.w3c.dom.Document doc = null;
try {
dBuilder = dbFactory.newDocumentBuilder();
doc = dBuilder.parse(new InputSource(new ByteArrayInputStream(response.getBytes("utf-8"))));
doc.getDocumentElement().normalize();
}catch (ParserConfigurationException | SAXException | IOException e) {
e.printStackTrace();
}
if(doc!=null){
NodeList nList = doc.getElementsByTagName("CompleteSuggestion");
for (int temp = 0; temp < nList.getLength(); temp++) {
NodeList nList1 = doc.getElementsByTagName("suggestion");
Node nNode1 = nList1.item(temp);
if (nNode1.getNodeType() == Node.ELEMENT_NODE) {
org.w3c.dom.Element eElement = (org.w3c.dom.Element) nNode1;
suggestions.add(eElement.getAttribute("data"));
}
}
}else {
Log.e(TAG, "GREAT FUCKING ERROR");
}
return suggestions;
}
}

View File

@@ -1,6 +1,9 @@
package org.schabi.newpipe.youtube;
import org.schabi.newpipe.StreamingService;
import org.schabi.newpipe.Extractor;
import org.schabi.newpipe.SearchEngine;
/**
* Created by Christian Schabesberger on 23.08.15.
@@ -30,12 +33,12 @@ public class YoutubeService implements StreamingService {
return serviceInfo;
}
@Override
public Class getExtractorClass() {
return YoutubeExtractor.class;
public Extractor getExtractorInstance() {
return new YoutubeExtractor();
}
@Override
public Class getSearchEngineClass() {
return YoutubeSearchEngine.class;
public SearchEngine getSearchEngineInstance() {
return new YoutubeSearchEngine();
}
@Override
public boolean acceptUrl(String videoUrl) {

View File

Before

Width:  |  Height:  |  Size: 3.3 KiB

After

Width:  |  Height:  |  Size: 3.3 KiB

View File

Before

Width:  |  Height:  |  Size: 2.6 KiB

After

Width:  |  Height:  |  Size: 2.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 209 B

View File

@@ -6,33 +6,34 @@
style="?android:attr/textAppearanceLarge"
tools:context=".VideoItemDetailFragment"
android:layout_width="match_parent"
android:layout_height="match_parent">
android:layout_height="match_parent"
android:id="@+id/videoitem_detail">
<ProgressBar android:id="@+id/detailProgressBar"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerInParent="true"
android:indeterminate="true"/>
<ScrollView
android:id="@+id/videoitem_detail"
android:id="@+id/detailMainContent"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:textIsSelectable="true">
android:textIsSelectable="true"
android:visibility="invisible">
<RelativeLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_height="wrap_content"
android:padding="16dp">
<ProgressBar android:id="@+id/detailProgressBar"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_gravity="center"
android:indeterminate="true"/>
<ImageView android:id="@+id/detailThumbnailView"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentLeft="true"
android:layout_alignParentTop="true"
android:adjustViewBounds="true"
android:src="@drawable/dummi_thumbnail"
android:visibility="invisible"/>
android:src="@drawable/dummy_thumbnail"/>
<TextView android:id="@+id/detailVideoTitleView"
android:layout_width="wrap_content"
@@ -40,19 +41,19 @@
android:layout_below="@id/detailThumbnailView"
android:layout_alignParentLeft="true"
android:paddingLeft="6dp"
android:paddingRight="6dp"
android:textAppearance="?android:attr/textAppearanceLarge"
android:text="Bla blabla !!!"
android:visibility="invisible"/>
android:text="Bla blabla !!!"/>
<ImageView android:id="@+id/detailUploaderThumbnailView"
android:layout_width="80dp"
android:layout_height="100dp"
android:paddingTop="25dp"
android:paddingLeft="5dp"
android:paddingRight="5dp"
android:layout_below="@id/detailVideoTitleView"
android:layout_alignParentLeft="true"
android:visibility="invisible"
android:src="@drawable/budy" />
android:src="@drawable/buddy" />
<TextView android:id="@+id/detailUploaderView"
android:layout_width="wrap_content"
@@ -60,8 +61,8 @@
android:layout_below="@id/detailUploaderThumbnailView"
android:layout_alignParentLeft="true"
android:paddingLeft="6dp"
android:paddingRight="6dp"
android:textAppearance="?android:attr/textAppearanceMedium"
android:visibility="invisible"
android:text="Herr von Gurken" />
<View android:id="@+id/textSeperationLine"
@@ -70,7 +71,6 @@
android:background="@android:color/darker_gray"
android:layout_below="@id/detailUploaderView"
android:paddingTop="20dp"
android:visibility="invisible"
android:layout_alignParentLeft="true" />
<TextView android:id="@+id/detailViewCountView"
@@ -80,7 +80,6 @@
android:layout_alignParentRight="true"
android:paddingTop="70dp"
android:textAppearance="?android:attr/textAppearanceLarge"
android:visibility="invisible"
android:text="1.000.115 views" />
<TextView android:id="@+id/detailThumbsDownCountView"
@@ -89,7 +88,6 @@
android:layout_below="@id/detailViewCountView"
android:layout_alignParentRight="true"
android:textAppearance="?android:attr/textAppearanceMedium"
android:visibility="invisible"
android:text="5.000" />
<ImageView android:id="@+id/detailThumbsDownImgView"
@@ -97,7 +95,6 @@
android:layout_height="20dp"
android:layout_below="@id/detailViewCountView"
android:layout_toLeftOf="@id/detailThumbsDownCountView"
android:visibility="invisible"
android:src="@drawable/thumbs_down" />
<TextView android:id="@+id/detailThumbsUpCountView"
@@ -106,7 +103,6 @@
android:layout_below="@id/detailViewCountView"
android:layout_toLeftOf="@id/detailThumbsDownImgView"
android:textAppearance="?android:attr/textAppearanceMedium"
android:visibility="invisible"
android:text="111.111" />
<ImageView android:id="@+id/detailThumbsUpImgView"
@@ -114,7 +110,6 @@
android:layout_height="20dp"
android:layout_below="@id/detailViewCountView"
android:layout_toLeftOf="@id/detailThumbsUpCountView"
android:visibility="invisible"
android:src="@drawable/thumbs_up" />
<TextView android:id="@+id/detailUploadDateView"
@@ -123,9 +118,9 @@
android:layout_below="@id/detailUploaderView"
android:layout_alignParentLeft="true"
android:paddingLeft="6dp"
android:paddingRight="6dp"
android:paddingTop="20dp"
android:textAppearance="?android:attr/textAppearanceLarge"
android:visibility="invisible"
android:text="Uploaded at: 45.64.1285" />
<TextView android:id="@+id/detailDescriptionView"
@@ -134,15 +129,77 @@
android:layout_below="@id/detailUploadDateView"
android:layout_alignParentLeft="true"
android:paddingLeft="6dp"
android:paddingRight="6dp"
android:textAppearance="?android:attr/textAppearanceMedium"
android:visibility="invisible"
android:text="Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmodtempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet. Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet. Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet. "
/>
<RelativeLayout android:id="@+id/detailNextVideoRootLayout"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_below="@id/detailDescriptionView" >
<TextView android:id="@+id/detailNextVideoTitle"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentLeft="true"
android:paddingLeft="6dp"
android:paddingRight="6dp"
android:paddingTop="20dp"
android:paddingBottom="6dp"
android:textAppearance="?android:attr/textAppearanceMedium"
android:textColor="@android:color/black"
android:text="@string/nextVideoTitle"
/>
<View android:id="@+id/detailNextVideoSeperationLine"
android:layout_width="fill_parent"
android:layout_height="1dp"
android:layout_marginLeft="6dp"
android:layout_marginRight="6dp"
android:background="@android:color/darker_gray"
android:layout_below="@id/detailNextVideoTitle"
android:layout_alignParentLeft="true" />
<RelativeLayout android:id="@+id/detailNextVidButtonAndContantLayout"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_below="@id/detailNextVideoSeperationLine">
<FrameLayout
android:id="@+id/detailNextVideoFrame"
android:layout_width="match_parent"
android:layout_height="wrap_content"/>
<Button
android:id="@+id/detailNextVideoButton"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_alignTop="@id/detailNextVideoFrame"
android:layout_alignBottom="@id/detailNextVideoFrame"
android:background="?attr/selectableItemBackground"/>
</RelativeLayout>
<View android:id="@+id/detailNextVideoSeperationLineEnd"
android:layout_width="fill_parent"
android:layout_height="1dp"
android:layout_marginLeft="6dp"
android:layout_marginRight="6dp"
android:background="@android:color/darker_gray"
android:layout_below="@id/detailNextVidButtonAndContantLayout"
android:layout_alignParentLeft="true" />
</RelativeLayout>
<Button android:id="@+id/detailShowSimilarButton"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginLeft="6dp"
android:layout_marginRight="6dp"
android:layout_below="@id/detailNextVideoRootLayout"
android:text="@string/showSimilarVideosButtonText"/>
<View
android:layout_width="match_parent"
android:layout_height="100dp"
android:layout_below="@id/detailDescriptionView"/>
android:layout_height="90dp"
android:layout_below="@id/detailShowSimilarButton"/>
</RelativeLayout>
</ScrollView>
@@ -155,5 +212,5 @@
android:layout_height="wrap_content"
app:backgroundTint="@color/primaryColorYoutube"
android:src="@drawable/ic_play_arrow_black"
android:layout_margin="15pt"/>
android:layout_margin="16dip"/>
</RelativeLayout>

View File

@@ -42,12 +42,6 @@
android:layout_width="0dp"
android:layout_weight="4">
<ImageView android:id="@+id/list_view_watermark"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_margin="10dp"
android:src="@drawable/new_pipe_watermark"/>
<FrameLayout android:id="@+id/videoitem_detail_container"
android:layout_width="match_parent"
android:layout_height="match_parent" />

View File

@@ -0,0 +1,216 @@
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
xmlns:app="http://schemas.android.com/apk/res-auto"
style="?android:attr/textAppearanceLarge"
tools:context=".VideoItemDetailFragment"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:id="@+id/videoitem_detail">
<ProgressBar android:id="@+id/detailProgressBar"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerInParent="true"
android:indeterminate="true"/>
<ScrollView
android:id="@+id/detailMainContent"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:textIsSelectable="true"
android:visibility="invisible">
<RelativeLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:padding="16dp">
<ImageView android:id="@+id/detailThumbnailView"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentLeft="true"
android:layout_alignParentTop="true"
android:adjustViewBounds="true"
android:src="@drawable/dummy_thumbnail"/>
<TextView android:id="@+id/detailVideoTitleView"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_below="@id/detailThumbnailView"
android:layout_alignParentLeft="true"
android:paddingLeft="6dp"
android:paddingRight="6dp"
android:textAppearance="?android:attr/textAppearanceLarge"
android:text="Bla blabla !!!"/>
<ImageView android:id="@+id/detailUploaderThumbnailView"
android:layout_width="80dp"
android:layout_height="100dp"
android:paddingTop="25dp"
android:paddingLeft="5dp"
android:paddingRight="5dp"
android:layout_below="@id/detailVideoTitleView"
android:layout_alignParentLeft="true"
android:src="@drawable/buddy" />
<TextView android:id="@+id/detailUploaderView"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_below="@id/detailUploaderThumbnailView"
android:layout_alignParentLeft="true"
android:paddingLeft="6dp"
android:paddingRight="6dp"
android:textAppearance="?android:attr/textAppearanceMedium"
android:text="Herr von Gurken" />
<View android:id="@+id/textSeperationLine"
android:layout_width="fill_parent"
android:layout_height="1dp"
android:background="@android:color/darker_gray"
android:layout_below="@id/detailUploaderView"
android:paddingTop="20dp"
android:layout_alignParentLeft="true" />
<TextView android:id="@+id/detailViewCountView"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_below="@id/detailVideoTitleView"
android:layout_alignParentRight="true"
android:paddingTop="70dp"
android:textAppearance="?android:attr/textAppearanceLarge"
android:text="1.000.115 views" />
<TextView android:id="@+id/detailThumbsDownCountView"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_below="@id/detailViewCountView"
android:layout_alignParentRight="true"
android:textAppearance="?android:attr/textAppearanceMedium"
android:text="5.000" />
<ImageView android:id="@+id/detailThumbsDownImgView"
android:layout_width="40dp"
android:layout_height="20dp"
android:layout_below="@id/detailViewCountView"
android:layout_toLeftOf="@id/detailThumbsDownCountView"
android:src="@drawable/thumbs_down" />
<TextView android:id="@+id/detailThumbsUpCountView"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_below="@id/detailViewCountView"
android:layout_toLeftOf="@id/detailThumbsDownImgView"
android:textAppearance="?android:attr/textAppearanceMedium"
android:text="111.111" />
<ImageView android:id="@+id/detailThumbsUpImgView"
android:layout_width="40dp"
android:layout_height="20dp"
android:layout_below="@id/detailViewCountView"
android:layout_toLeftOf="@id/detailThumbsUpCountView"
android:src="@drawable/thumbs_up" />
<TextView android:id="@+id/detailUploadDateView"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_below="@id/detailUploaderView"
android:layout_alignParentLeft="true"
android:paddingLeft="6dp"
android:paddingRight="6dp"
android:paddingTop="20dp"
android:textAppearance="?android:attr/textAppearanceLarge"
android:text="Uploaded at: 45.64.1285" />
<TextView android:id="@+id/detailDescriptionView"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_below="@id/detailUploadDateView"
android:layout_alignParentLeft="true"
android:paddingLeft="6dp"
android:paddingRight="6dp"
android:textAppearance="?android:attr/textAppearanceMedium"
android:text="Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmodtempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet. Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet. Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet. "
/>
<RelativeLayout android:id="@+id/detailNextVideoRootLayout"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_below="@id/detailDescriptionView" >
<TextView android:id="@+id/detailNextVideoTitle"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentLeft="true"
android:paddingLeft="6dp"
android:paddingRight="6dp"
android:paddingTop="20dp"
android:paddingBottom="6dp"
android:textAppearance="?android:attr/textAppearanceMedium"
android:textColor="@android:color/black"
android:text="@string/nextVideoTitle"
/>
<View android:id="@+id/detailNextVideoSeperationLine"
android:layout_width="fill_parent"
android:layout_height="1dp"
android:layout_marginLeft="6dp"
android:layout_marginRight="6dp"
android:background="@android:color/darker_gray"
android:layout_below="@id/detailNextVideoTitle"
android:layout_alignParentLeft="true" />
<RelativeLayout android:id="@+id/detailNextVidButtonAndContantLayout"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_below="@id/detailNextVideoSeperationLine">
<FrameLayout
android:id="@+id/detailNextVideoFrame"
android:layout_width="match_parent"
android:layout_height="wrap_content"/>
<Button
android:id="@+id/detailNextVideoButton"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_alignTop="@id/detailNextVideoFrame"
android:layout_alignBottom="@id/detailNextVideoFrame"
android:background="?attr/selectableItemBackground"/>
</RelativeLayout>
<View android:id="@+id/detailNextVideoSeperationLineEnd"
android:layout_width="fill_parent"
android:layout_height="1dp"
android:layout_marginLeft="6dp"
android:layout_marginRight="6dp"
android:background="@android:color/darker_gray"
android:layout_below="@id/detailNextVidButtonAndContantLayout"
android:layout_alignParentLeft="true" />
</RelativeLayout>
<Button android:id="@+id/detailShowSimilarButton"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginLeft="6dp"
android:layout_marginRight="6dp"
android:layout_below="@id/detailNextVideoRootLayout"
android:text="@string/showSimilarVideosButtonText"/>
<View
android:layout_width="match_parent"
android:layout_height="90dp"
android:layout_below="@id/detailShowSimilarButton"/>
</RelativeLayout>
</ScrollView>
<android.support.design.widget.FloatingActionButton
android:id="@+id/playVideoButton"
android:layout_alignParentRight="true"
android:layout_alignParentBottom="true"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:backgroundTint="@color/primaryColorYoutube"
android:src="@drawable/ic_play_arrow_black"
android:layout_margin="16dip"/>
</RelativeLayout>

View File

@@ -3,12 +3,6 @@
android:layout_height="match_parent"
android:orientation="vertical">
<ImageView android:id="@+id/list_view_watermark"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_margin="10dp"
android:src="@drawable/new_pipe_watermark"/>
<fragment
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/videoitem_list"

View File

@@ -7,21 +7,24 @@
android:textIsSelectable="true"
style="?android:attr/textAppearanceLarge"
android:layout_width="match_parent"
android:layout_height="match_parent">
android:layout_height="match_parent"
android:id="@+id/videoitem_detail">
<ProgressBar android:id="@+id/detailProgressBar"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerInParent="true"
android:indeterminate="true"/>
<ScrollView
android:id="@+id/videoitem_detail"
android:id="@+id/detailMainContent"
android:layout_width="match_parent"
android:layout_height="match_parent">
android:layout_height="match_parent"
android:visibility="invisible">
<RelativeLayout
android:layout_width="match_parent"
android:layout_height="match_parent">
<ProgressBar android:id="@+id/detailProgressBar"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_gravity="center"
android:indeterminate="true"/>
android:layout_height="wrap_content">
<ImageView android:id="@+id/detailThumbnailView"
android:layout_width="match_parent"
@@ -30,8 +33,7 @@
android:layout_alignParentLeft="true"
android:layout_alignParentTop="true"
android:adjustViewBounds="true"
android:visibility="invisible"
android:src="@drawable/dummi_thumbnail"/>
android:src="@drawable/dummy_thumbnail"/>
<TextView android:id="@+id/detailVideoTitleView"
android:layout_width="wrap_content"
@@ -39,20 +41,22 @@
android:layout_below="@id/detailThumbnailView"
android:layout_alignParentLeft="true"
android:paddingLeft="6dp"
android:paddingBottom="20dp"
android:paddingRight="6dp"
android:paddingBottom="0dp"
android:paddingTop="3dp"
android:textStyle="bold"
android:textAppearance="?android:attr/textAppearanceLarge"
android:visibility="invisible"
android:text="Bla blabla !!!"/>
android:text="Video title placeholder"/>
<ImageView android:id="@+id/detailUploaderThumbnailView"
android:layout_width="85dp"
android:layout_height="100dp"
android:paddingTop="25dp"
android:paddingTop="0dp"
android:paddingLeft="2dp"
android:paddingRight="2dp"
android:layout_below="@id/detailVideoTitleView"
android:layout_alignParentLeft="true"
android:visibility="invisible"
android:src="@drawable/budy" />
android:src="@drawable/buddy" />
<TextView android:id="@+id/detailUploaderView"
android:layout_width="wrap_content"
@@ -60,20 +64,23 @@
android:layout_below="@id/detailUploaderThumbnailView"
android:layout_alignParentLeft="true"
android:paddingLeft="6dp"
android:textAppearance="?android:attr/textAppearanceMedium"
android:visibility="invisible"
android:text="Herr von Gurken" />
android:paddingRight="6dp"
android:textStyle="bold"
android:textSize="@dimen/text_video_uploader_size"
android:textAppearance="?android:attr/textAppearanceLarge"
android:text="username" />
<TextView android:id="@+id/detailViewCountView"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:paddingTop="25dp"
android:paddingTop="6dp"
android:layout_below="@id/detailVideoTitleView"
android:layout_alignParentRight="true"
android:paddingRight="16dp"
android:paddingLeft="16dp"
android:textSize="@dimen/text_video_visits_size"
android:textAppearance="?android:attr/textAppearanceLarge"
android:visibility="invisible"
android:text="drölf views" />
android:text="81,754 views" />
<TextView android:id="@+id/detailThumbsDownCountView"
android:layout_width="wrap_content"
@@ -81,16 +88,16 @@
android:layout_below="@id/detailViewCountView"
android:layout_alignParentRight="true"
android:paddingRight="16dp"
android:paddingLeft="16dp"
android:textSize="@dimen/text_video_like_size"
android:textAppearance="?android:attr/textAppearanceMedium"
android:visibility="invisible"
android:text="-5.000" />
android:text="100" />
<ImageView android:id="@+id/detailThumbsDownImgView"
android:layout_width="40dp"
android:layout_height="20dp"
android:layout_below="@id/detailViewCountView"
android:layout_toLeftOf="@id/detailThumbsDownCountView"
android:visibility="invisible"
android:src="@drawable/thumbs_down" />
<TextView android:id="@+id/detailThumbsUpCountView"
@@ -98,16 +105,15 @@
android:layout_height="wrap_content"
android:layout_below="@id/detailViewCountView"
android:layout_toLeftOf="@id/detailThumbsDownImgView"
android:textSize="@dimen/text_video_like_size"
android:textAppearance="?android:attr/textAppearanceMedium"
android:visibility="invisible"
android:text="∞" />
android:text="20" />
<ImageView android:id="@+id/detailThumbsUpImgView"
android:layout_width="40dp"
android:layout_height="20dp"
android:layout_below="@id/detailViewCountView"
android:layout_toLeftOf="@id/detailThumbsUpCountView"
android:visibility="invisible"
android:src="@drawable/thumbs_up" />
<TextView android:id="@+id/detailUploadDateView"
@@ -116,29 +122,95 @@
android:layout_below="@id/detailUploaderView"
android:layout_alignParentLeft="true"
android:paddingLeft="6dp"
android:paddingTop="20dp"
android:paddingRight="6dp"
android:paddingTop="0dp"
android:textSize="@dimen/text_video_uploadtime_size"
android:textAppearance="?android:attr/textAppearanceLarge"
android:visibility="invisible"
android:text="Uploaded at: 45.64.1285" />
android:text="Published on Jan 01 1975" />
<TextView android:id="@+id/detailDescriptionView"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_below="@id/detailUploadDateView"
android:layout_alignParentLeft="true"
android:paddingTop="3dp"
android:paddingLeft="6dp"
android:paddingRight="6dp"
android:textSize="@dimen/text_video_description_size"
android:textAppearance="?android:attr/textAppearanceMedium"
android:visibility="invisible"
android:text="Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmodtempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet. Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet. Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet. "
/>
<RelativeLayout android:id="@+id/detailNextVideoRootLayout"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_below="@id/detailDescriptionView" >
<TextView android:id="@+id/detailNextVideoTitle"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentLeft="true"
android:paddingLeft="6dp"
android:paddingRight="6dp"
android:paddingTop="20dp"
android:paddingBottom="6dp"
android:textAppearance="?android:attr/textAppearanceMedium"
android:textColor="@android:color/black"
android:text="@string/nextVideoTitle"
/>
<View android:id="@+id/detailNextVideoSeperationLine"
android:layout_width="fill_parent"
android:layout_height="1dp"
android:layout_marginLeft="6dp"
android:layout_marginRight="6dp"
android:background="@android:color/darker_gray"
android:layout_below="@id/detailNextVideoTitle"
android:layout_alignParentLeft="true" />
<RelativeLayout android:id="@+id/detailNextVidButtonAndContantLayout"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_below="@id/detailNextVideoSeperationLine">
<FrameLayout
android:id="@+id/detailNextVideoFrame"
android:layout_width="match_parent"
android:layout_height="wrap_content"/>
<Button
android:id="@+id/detailNextVideoButton"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_alignTop="@id/detailNextVideoFrame"
android:layout_alignBottom="@id/detailNextVideoFrame"
android:background="?attr/selectableItemBackground"/>
</RelativeLayout>
<View android:id="@+id/detailNextVideoSeperationLineEnd"
android:layout_width="fill_parent"
android:layout_height="1dp"
android:layout_marginLeft="6dp"
android:layout_marginRight="6dp"
android:background="@android:color/darker_gray"
android:layout_below="@id/detailNextVidButtonAndContantLayout"
android:layout_alignParentLeft="true" />
</RelativeLayout>
<Button android:id="@+id/detailShowSimilarButton"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginLeft="6dp"
android:layout_marginRight="6dp"
android:layout_below="@id/detailNextVideoRootLayout"
android:text="@string/showSimilarVideosButtonText"/>
<View
android:layout_width="match_parent"
android:layout_height="100dp"
android:layout_below="@id/detailDescriptionView"/>
android:layout_height="90dp"
android:layout_below="@id/detailShowSimilarButton"/>
</RelativeLayout>
</ScrollView>
<android.support.design.widget.FloatingActionButton
android:id="@+id/playVideoButton"
android:layout_alignParentBottom="true"
@@ -147,5 +219,5 @@
android:layout_height="wrap_content"
app:backgroundTint="@color/primaryColorYoutube"
android:src="@drawable/ic_play_arrow_black"
android:layout_margin="15pt"/>
android:layout_margin="20dp"/>
</RelativeLayout>

View File

@@ -7,11 +7,12 @@
android:padding="6dp">
<ImageView android:id="@+id/itemThumbnailView"
android:layout_width="125dp"
android:layout_width="142dp"
android:layout_height="80dp"
android:scaleType="centerCrop"
android:layout_alignParentLeft="true"
android:layout_alignParentTop="true"
android:src="@drawable/dummi_thumbnail"/>
android:src="@drawable/dummy_thumbnail"/>
<TextView android:id="@+id/itemVideoTitleView"
android:layout_width="wrap_content"

View File

@@ -1,16 +1,17 @@
<?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/menu_item_play"
android:title="@string/play"
app:showAsAction="ifRoom"
android:icon="@drawable/ic_play_arrow_black"/>
<item android:id="@+id/menu_item_play_audio"
android:title="@string/playAudio"
app:showAsAction="always"
app:showAsAction="ifRoom"
android:icon="@drawable/ic_headset_black" />
<item android:id="@+id/menu_item_download"
app:showAsAction="ifRoom"
android:title="@string/download"
android:icon="@drawable/ic_file_download_black"/>
<item android:id="@+id/menu_item_share"
android:title="@string/share"
app:showAsAction="ifRoom"
@@ -25,10 +26,6 @@
app:showAsAction="never"
android:title="@string/open_in_browser" />
<item android:id="@+id/menu_item_download"
app:showAsAction="never"
android:title="@string/download"/>
<item android:id="@+id/action_settings"
app:showAsAction="never"
android:title="@string/settings"/>

View 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_settings"
app:showAsAction="never"
android:title="@string/settings"/>
</menu>

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.3 KiB

After

Width:  |  Height:  |  Size: 3.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.4 KiB

After

Width:  |  Height:  |  Size: 1.9 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.0 KiB

After

Width:  |  Height:  |  Size: 4.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.6 KiB

After

Width:  |  Height:  |  Size: 7.1 KiB

View File

@@ -1,10 +1,10 @@
<?xml version="1.0" encoding="utf-8"?>
<?xml version='1.0' encoding='utf-8'?>
<resources>
<string name="app_name">NewPipe</string>
<string name="title_videoitem_detail">NewPipe</string>
<string name="nothingFound">Nichts gefunden</string>
<string name="viewSufix">views</string>
<string name="uploadDatePrefix">Hochgeladen am: </string>
<string name="viewSufix">Aufrufe</string>
<string name="uploadDatePrefix">Hochgeladen am </string>
<string name="noPlayerFound">Keinen Streamplayer gefunden. Vielleicht möchtest du einen installieren.</string>
<string name="installStreamPlayer">Jetzt installieren</string>
<string name="cancel">Abbrechen</string>
@@ -26,7 +26,7 @@
<string name="downloadLocation">Download Verzeichnis</string>
<string name="downloadLocationSummary">Verzeichnis in dem heruntergeladene Videos gespeichert werden.</string>
<string name="downloadLocationDialogTitle">Download Verzeichnis eingeben</string>
<string name="autoPlayThroughIntentTitle">Automatisch abspielen durch Intent.</string>
<string name="autoPlayThroughIntentTitle">Automatisches abspielen durch Intent</string>
<string name="autoPlayThroughIntentSummary">Startet ein Video automatisch wenn es von einer anderen App aufgerufen wurde.</string>
<string name="defaultResolutionPreferenceTitle">Standard Auflösung</string>
<string name="playWithKodiTitle">Mit Kodi abspielen</string>
@@ -35,7 +35,7 @@
<string name="fdroidKoreUrl">https://f-droid.org/repository/browse/?fdfilter=Kore&amp;fdid=org.xbmc.kore</string>
<string name="showPlayWithKodiTitle">Zeige \"Mit Kodi abspielen\" Option</string>
<string name="showPlayWithKodiSummary">Zeigt eine Option an, über die man Videos mit dem Kodi Mediacenter abspielen kann.</string>
<string name="leftHandLayoutTitle">Linkshänder freundliches Layout.</string>
<string name="leftPlayButtonTitle">Zeige play button auf der linken seite.</string>
<string name="playAudio">Audio</string>
<string name="defaultAudioFormatTitle">Bevorzugtes Audio Format</string>
<string name="webMAudioDescription">WebM - freies Format</string>
@@ -45,4 +45,8 @@
<item>Video</item>
<item>Audio</item>
</string-array>
<string name="nextVideoTitle">Nächstes Video</string>
<string name="showNextAndSimilarTitle">Zeige nächstes und änliche Videos</string>
<string name="urlNotSupportedText">URL wird nicht unterstützt.</string>
<string name="showSimilarVideosButtonText">Ähnliche Videos</string>
</resources>

View File

@@ -1,22 +1,22 @@
<?xml version="1.0" encoding="utf-8"?>
<?xml version='1.0' encoding='utf-8'?>
<resources>
<string name="app_name">NewPipe</string>
<string name="title_videoitem_detail">NewPipe</string>
<string name="nothingFound">No se ha encontrado nada</string>
<string name="viewSufix">visitas</string>
<string name="uploadDatePrefix">Subido desde: </string>
<string name="uploadDatePrefix">Subido el </string>
<string name="noPlayerFound">No se ha encontrado ningún reproductor de vídeo. Quizás quieras instalar alguno.</string>
<string name="installStreamPlayer">Instalar</string>
<string name="installStreamPlayer">Instalarlo</string>
<string name="cancel">Cancelar</string>
<string name="fdroidVLCurl">https://f-droid.org/repository/browse/?fdfilter=vlc&amp;fdid=org.videolan.vlc</string>
<string name="open_in_browser">Abrir en navegador</string>
<string name="open_in_browser">Abrir en el navegador</string>
<string name="share">Compartir</string>
<string name="play">Reproducir</string>
<string name="download">Descargar</string>
<string name="search">Buscar</string>
<string name="settings">Ajustes</string>
<string name="sendWith">Enviar con</string>
<string name="didYouMean">Querias decir: </string>
<string name="didYouMean">"¿Querías decir?: "</string>
<string name="searchPage">Buscar página: </string>
<string name="shareDialogTitle">Compartir con:</string>
<string name="chooseBrowser">Selecciona navegador:</string>
@@ -24,15 +24,26 @@
<string name="title_activity_settings">Ajustes</string>
<string name="useExternalPlayerTitle">Usar reproductor externo</string>
<string name="downloadLocation">Descargar en...</string>
<string name="downloadLocationSummary">Donde se guardarán los vídeos descargados.</string>
<string name="downloadLocationSummary">Ruta donde guardar los vídeos descargados.</string>
<string name="downloadLocationDialogTitle">Localización del directorio de descargas</string>
<string name="autoPlayThroughIntentTitle">Reproducción automática</string>
<string name="autoPlayThroughIntentSummary">Reproduce los vídeos automaticamente cuando la llamada viene de otra aplicación.</string>
<string name="autoPlayThroughIntentSummary">Reproducir los vídeos automaticamente cuando se llama desde otra aplicación.</string>
<string name="defaultResolutionPreferenceTitle">Resolución por defecto</string>
<string name="playWithKodiTitle">Reproducir con Kodi</string>
<string name="koreNotFound">Aplicación Kore no encontrada. Kore es necesario para reproducir vídeos con Kodi media center.</string>
<string name="installeKore">Instalar Kore</string>
<string name="fdroidKoreUrl">https://f-droid.org/repository/browse/?fdfilter=Kore&amp;fdid=org.xbmc.kore</string>
<string name="showPlayWithKodiTitle">Mostrar la opción \"Reproducir con Kodi\"</string>
<string name="showPlayWithKodiSummary">Muestra una opción para reproducir vídeo vía Kodi media center.</string>
<string name="showPlayWithKodiSummary">Muestra una opción para reproducir el vídeo con Kodi media center.</string>
<string name="leftPlayButtonTitle">Mostrar el botón de reproducir en el lado izquierdo.</string>
<string name="playAudio">Audio</string>
<string name="defaultAudioFormatTitle">Formato de audio por defecto</string>
<string name="webMAudioDescription">WebM - formato libre</string>
<string name="m4aAudioDescription">m4a - mejor calidad</string>
<string name="downloadDialogTitle">Descargar</string>
<string name="nextVideoTitle">Siguiente vídeo</string>
<string name="showNextVideoTitle">Mostrar la opción \"Siguiente vídeo\".</string>
<string name="urlNotSupportedText">URL no soportada.</string>
<string name="showSimilarVideosButtonText">Vídeos similares</string>
<string name="contentCountryTitle">País del contenido del vídeo</string>
</resources>

View File

@@ -0,0 +1,51 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name="app_name">NewPipe</string>
<string name="title_videoitem_detail">NewPipe</string>
<string name="nothingFound">چیزی پیدا نشد</string>
<string name="viewSufix">نماها</string>
<string name="uploadDatePrefix">بارگذاری‌شده در: </string>
<string name="noPlayerFound">هیچ پخش‌کننده‌ی جریانی یافت نشد. ممکن است بخواهید یکی نصب کنید.</string>
<string name="installStreamPlayer">نصب کنید</string>
<string name="cancel">انصراف</string>
<string name="fdroidVLCurl">https://f-droid.org/repository/browse/?fdfilter=vlc&amp;fdid=org.videolan.vlc</string>
<string name="open_in_browser">بازکردن در مرورگر</string>
<string name="share">هم‌رسانی</string>
<string name="play">پخش</string>
<string name="download">بارگیری</string>
<string name="search">جستجو</string>
<string name="settings">تنظیمات</string>
<string name="sendWith">فرستادن با</string>
<string name="didYouMean">منظورتان این است: </string>
<string name="searchPage">صفحه‌ی جستجو: </string>
<string name="shareDialogTitle">هم‌رسانی با:</string>
<string name="chooseBrowser">مرورگر را برگزینید:</string>
<string name="screenRotation">چرخش</string>
<string name="title_activity_settings">تنظیمات</string>
<string name="useExternalPlayerTitle">استفاده از پخش‌کننده‌ی خارجی</string>
<string name="downloadLocation">محل بارگیری</string>
<string name="downloadLocationSummary">مسیری که ویدئوهای دریافت شده در آن ذخیره می‌شوند.</string>
<string name="downloadLocationDialogTitle">مسیر دریافت را وارد کنید</string>
<string name="autoPlayThroughIntentTitle">پخش خودکار از Intent</string>
<string name="autoPlayThroughIntentSummary">ویدئو هنگامی که از برنامه‌ی دیگری فراخوانده شد خودکار پخش می‌شود.</string>
<string name="defaultResolutionPreferenceTitle">وضوح پیش‌فرض</string>
<string name="playWithKodiTitle">پخش با Kodi</string>
<string name="koreNotFound">برنامه‌ی Kore نصب نیست. برای پخش کردن ویدئوها با مرکز رسانه‌ی Kodi، به Kore نیاز دارید.</string>
<string name="installeKore">نصب Kore</string>
<string name="fdroidKoreUrl">https://f-droid.org/repository/browse/?fdfilter=Kore&amp;fdid=org.xbmc.kore</string>
<string name="showPlayWithKodiTitle">نمایش گزینه‌ی «پخش با Kodi»</string>
<string name="showPlayWithKodiSummary">گزینه‌ای برای پخش کردن ویدئو با مرکز رسانه‌ی Kodi نشان می‌دهد.</string>
<string name="leftPlayButtonTitle">نمایش دکمه‌ی پخش در سمت چپ.</string>
<string name="playAudio">صدا</string>
<string name="defaultAudioFormatTitle">قالب پیش‌فرض صدا</string>
<string name="webMAudioDescription">WebM - قالبی آزاد</string>
<string name="m4aAudioDescription">m4a - کیفیت بهتر</string>
<string name="downloadDialogTitle">دریافت</string>
<string-array name="downloadOptions">
<item>ویدئو</item>
<item>صدا</item>
</string-array>
<string name="nextVideoTitle">ویدئوی بعدی</string>
<string name="showNextVideoTitle">نمایش گزینه‌ی «ویدئوی بعدی».</string>
<string name="urlNotSupportedText">پیوند پشتیبانی نمی‌شود.</string>
</resources>

View File

@@ -1,4 +1,4 @@
<?xml version="1.0" encoding="utf-8"?>
<?xml version='1.0' encoding='utf-8'?>
<resources>
<string name="app_name">NewPipe</string>
<string name="autoPlayThroughIntentSummary">Démarrer automatiquement la vidéo si elle a été appellée à partir d\'une autre application.</string>
@@ -16,10 +16,10 @@
<string name="installeKore">Installer Kore</string>
<string name="koreNotFound">L\'application Kore est introuvable. Kore est nécessaire afin de lire des vidéos dans Kodi media center.</string>
<string name="noPlayerFound">Aucun lecteur de streaming détecté. Vous devriez en installer un.</string>
<string name="nothingFound">Aucun résultat.</string>
<string name="nothingFound">Aucun résultat</string>
<string name="open_in_browser">Ouvrir dans le navigateur</string>
<string name="play">Lire</string>
<string name="autoPlayThroughIntentTitle">Lecture automatique via Intent.</string>
<string name="autoPlayThroughIntentTitle">Lecture automatique via Intent</string>
<string name="playWithKodiTitle">Lire avec Kodi</string>
<string name="screenRotation">rotation</string>
<string name="search">Chercher</string>
@@ -32,7 +32,7 @@
<string name="showPlayWithKodiTitle">Afficher l\'option \"Lire avec Kodi\"</string>
<string name="title_activity_settings">Paramètres</string>
<string name="title_videoitem_detail">NewPipe</string>
<string name="uploadDatePrefix">Envoyé le:</string>
<string name="uploadDatePrefix">Mise en ligne le </string>
<string name="useExternalPlayerTitle">Utiliser un lecteur externe</string>
<string name="viewSufix">vues</string>
</resources>
</resources>

View File

@@ -1,4 +1,4 @@
<?xml version="1.0" encoding="utf-8"?>
<?xml version='1.0' encoding='utf-8'?>
<resources>
<string name="app_name">NewPipe</string>
<string name="title_videoitem_detail">NewPipe</string>
@@ -35,4 +35,18 @@
<string name="fdroidKoreUrl">https://f-droid.org/repository/browse/?fdfilter=Kore&amp;fdid=org.xbmc.kore</string>
<string name="showPlayWithKodiTitle">\"Lejátszás Kodi-val\" opció mutatása</string>
<string name="showPlayWithKodiSummary">Mutat egy opciót a videók Kodi médiaközponttal való lejátszására</string>
</resources>
<string name="leftPlayButtonTitle">Lejátszás gomb bal oldalon mutatása</string>
<string name="playAudio">Hang</string>
<string name="defaultAudioFormatTitle">Alapértelmezett hang formátum</string>
<string name="webMAudioDescription">WebM - szabad formátum</string>
<string name="m4aAudioDescription">m4a - jobb minőség</string>
<string name="downloadDialogTitle">Letöltés</string>
<string-array name="downloadOptions">
<item>Videó</item>
<item>Hang</item>
</string-array>
<string name="nextVideoTitle">Következő videó</string>
<string name="showNextVideoTitle">\"Következő videó\" elem mutatása</string>
<string name="urlNotSupportedText">A webcím nem támogatott.</string>
<string name="showSimilarVideosButtonText">Hasonló videók</string>
</resources>

View File

@@ -4,8 +4,8 @@
<string name="title_videoitem_detail">NewPipe</string>
<string name="nothingFound">Geen resultaten</string>
<string name="viewSufix">keer bekeken</string>
<string name="uploadDatePrefix">Geüpload op: </string>
<string name="noPlayerFound">Geen speler met streaming ondersteuning gevonden. Je wilt er misschien een installeren.</string>
<string name="uploadDatePrefix">Geüpload op </string>
<string name="noPlayerFound">Geen speler met streaming ondersteuning gevonden. Misschien wil je er een installeren.</string>
<string name="installStreamPlayer">Installeer speler</string>
<string name="cancel">Annuleer</string>
<string name="fdroidVLCurl">https://f-droid.org/repository/browse/?fdfilter=vlc&amp;fdid=org.videolan.vlc</string>
@@ -35,4 +35,16 @@
<string name="fdroidKoreUrl">https://f-droid.org/repository/browse/?fdfilter=Kore&amp;fdid=org.xbmc.kore</string>
<string name="showPlayWithKodiTitle">Toon \"Speel af met Kodi\" optie</string>
<string name="showPlayWithKodiSummary">Toont een optie om een video op een Kodi media center af te spelen.</string>
</resources>
<string name="contentCountryTitle">Video inhoud land</string>
<string name="leftPlayButtonTitle">Afspeel knop aan de linker kant weergeven.</string>
<string name="playAudio">Audio</string>
<string name="defaultAudioFormatTitle">Standaard audio formaat</string>
<string name="webMAudioDescription">Webam - open formaat</string>
<string name="m4aAudioDescription">m4a - betere kwaliteit</string>
<string name="downloadDialogTitle">Download</string>
<string name="nextVideoTitle">Volgende video</string>
<string name="showNextVideoTitle">\"Volgende video\" weergeven</string>
<string name="urlNotSupportedText">URL wordt niet ondersteund.</string>
<string name="showSimilarVideosButtonText">Vergelijkbare videos</string>
<string name="showNextAndSimilarTitle">Laat volgende en vergelijkbare videos zien</string>
</resources>

View File

@@ -0,0 +1,54 @@
<?xml version='1.0' encoding='utf-8'?>
<resources>
<string name="app_name">NewPipe</string>
<string name="title_videoitem_detail">NewPipe</string>
<string name="nothingFound">Ничего не найдено</string>
<string name="viewSufix">просмотров</string>
<string name="uploadDatePrefix">Опубликовано: </string>
<string name="noPlayerFound">Ни одного потокового проигрывателя не было найдено. Установить?</string>
<string name="installStreamPlayer">Установить</string>
<string name="cancel">Отмена</string>
<string name="fdroidVLCurl">https://f-droid.org/repository/browse/?fdfilter=vlc&amp;fdid=org.videolan.vlc</string>
<string name="open_in_browser">Открыть в браузере</string>
<string name="share">Поделиться</string>
<string name="play">Воспроизвести</string>
<string name="download">Скачать</string>
<string name="search">Найти</string>
<string name="settings">Настройки</string>
<string name="sendWith">Отправить с помощью</string>
<string name="didYouMean">Возможно, вы имели в виду: </string>
<string name="searchPage">Страница поиска: </string>
<string name="shareDialogTitle">Поделиться с помощью:</string>
<string name="chooseBrowser">Выбрать браузер:</string>
<string name="screenRotation">поворот</string>
<string name="title_activity_settings">Настройки</string>
<string name="useExternalPlayerTitle">Использовать внешний проигрыватель</string>
<string name="downloadLocation">Место для загрузок</string>
<string name="downloadLocationSummary">Папка для хранения загруженных файлов.</string>
<string name="downloadLocationDialogTitle">Введите путь к папке для загрузок</string>
<string name="autoPlayThroughIntentTitle">Автопроигрывание через интернет</string>
<string name="autoPlayThroughIntentSummary">Автоматически воспроизводить видео когда оно открыто через другое приложение.</string>
<string name="defaultResolutionPreferenceTitle">Разрешение по-умолчанию</string>
<string name="playWithKodiTitle">Воспроизвести с помощью Kodi</string>
<string name="koreNotFound">Приложение Kore не наидено. Чтобы проигрывать видео через Kodi media center, нужен Kore.</string>
<string name="installeKore">Установить Kore</string>
<string name="fdroidKoreUrl">https://f-droid.org/repository/browse/?fdfilter=Kore&amp;fdid=org.xbmc.kore</string>
<string name="showPlayWithKodiTitle">Показывать опцию \"Воспроизвести с помощью Kodi\"</string>
<string name="showPlayWithKodiSummary">Показать опцию воспроизведения видео через Kodi media center.</string>
<string name="leftPlayButtonTitle">Показать кнопку воспроизведения слева.</string>
<string name="playAudio">Аудио</string>
<string name="defaultAudioFormatTitle">Формат аудио по-умолчанию</string>
<string name="webMAudioDescription">WebM - свободный формат</string>
<string name="m4aAudioDescription">m4a - лучшее качество</string>
<string name="downloadDialogTitle">Скачать</string>
<string-array name="downloadOptions">
<item>Видео</item>
<item>Аудио</item>
</string-array>
<string name="nextVideoTitle">Следующее видео</string>
<string name="showNextVideoTitle">Показать \"Следующее видео\".</string>
<string name="urlNotSupportedText">URL не поддерживается.</string>
<string name="showSimilarVideosButtonText">Похожие видео</string>
<string name="showNextAndSimilarTitle">Показывать следующее и предложенные видео</string>
<string name="searchLanguageTitle">Предпочитаемый язык контента</string>
</resources>

View File

@@ -1,4 +1,4 @@
<?xml version="1.0" encoding="utf-8"?>
<?xml version='1.0' encoding='utf-8'?>
<resources>
<string name="app_name">Јутјуб цев</string>
<string name="title_videoitem_detail">Јутјуб цев</string>
@@ -29,4 +29,23 @@
<string name="autoPlayThroughIntentTitle">Аутопуштање преко Интента</string>
<string name="autoPlayThroughIntentSummary">Аутоматски почиње пушта видео по позиву из друге апликације.</string>
<string name="defaultResolutionPreferenceTitle">Подразумевана резолуција</string>
<string name="playWithKodiTitle">Пусти помоћу Кодија</string>
<string name="koreNotFound">Апликација Кор није нађена. Кор (Kore) је потребан да бисте пуштали видее у Коди медија центру.</string>
<string name="installeKore">Инсталирај Кор</string>
<string name="fdroidKoreUrl">https://f-droid.org/repository/browse/?fdfilter=Kore&amp;fdid=org.xbmc.kore</string>
<string name="showPlayWithKodiTitle">Прикажи „Пусти помоћу Кодија“</string>
<string name="showPlayWithKodiSummary">Приказ опције за пуштање видеа у Коди медија центру.</string>
<string name="leftPlayButtonTitle">Прикажи дугме за пуштање на левој страни.</string>
<string name="playAudio">Аудио</string>
<string name="defaultAudioFormatTitle">Подразумевани формат звука</string>
<string name="webMAudioDescription">WebM - слободни формат</string>
<string name="m4aAudioDescription">m4a - бољи квалитет</string>
<string name="downloadDialogTitle">Преузми</string>
<string-array name="downloadOptions">
<item>Видео</item>
<item>Аудио</item>
</string-array>
<string name="nextVideoTitle">Следећи видео</string>
<string name="showNextVideoTitle">Приказ ставке „Следећи видео“.</string>
<string name="urlNotSupportedText">УРЛ није подржан.</string>
</resources>

View File

@@ -1,7 +1,7 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<color name="primaryColorYoutube">#dd0000</color>
<color name="primaryColorDarkYoutube">#bb0000</color>
<color name="primaryColorYoutube">#cd322e</color>
<color name="primaryColorDarkYoutube">#bc211d</color>
<color name="accentColorYoutube">#000000</color>
<color name="black_overlay">#66000000</color>
</resources>

View File

@@ -4,4 +4,10 @@
<dimen name="text_search_duration_size">11sp</dimen>
<dimen name="text_search_uploader_size">12sp</dimen>
<dimen name="text_search_uploadtime_size">12sp</dimen>
<dimen name="text_video_title_size">14sp</dimen>
<dimen name="text_video_visits_size">14sp</dimen>
<dimen name="text_video_like_size">12sp</dimen>
<dimen name="text_video_uploader_size">14sp</dimen>
<dimen name="text_video_uploadtime_size">14sp</dimen>
<dimen name="text_video_description_size">14sp</dimen>
</resources>

View File

@@ -23,4 +23,164 @@
<item>m4a</item>
</string-array>
<string name="defaultAudioFormat">m4a</string>
<string name="showNextVideo">show_next_video</string>
<string name="searchLanguage">search_language</string>
<!-- TODO: scrape these programmatically, then store in a local cache -->
<!-- alternatively, load these from some local android data store -->
<string-array name='languageCodes'>
<item>af</item>
<item>az</item>
<item>id</item>
<item>ms</item>
<item>ca</item>
<item>cs</item>
<item>da</item>
<item>de</item>
<item>et</item>
<item>en-GB</item>
<item>en</item>
<item>es</item>
<item>es-419</item>
<item>eu</item>
<item>fil</item>
<item>fr</item>
<item>fr-CA</item>
<item>gl</item>
<item>hr</item>
<item>zu</item>
<item>is</item>
<item>it</item>
<item>sw</item>
<item>lv</item>
<item>lt</item>
<item>hu</item>
<item>nl</item>
<item>no</item>
<item>uz</item>
<item>pl</item>
<item>pt-PT</item>
<item>pt</item>
<item>ro</item>
<item>sq</item>
<item>sk</item>
<item>sl</item>
<item>fi</item>
<item>sv</item>
<item>vi</item>
<item>tr</item>
<item>bg</item>
<item>ky</item>
<item>kk</item>
<item>mk</item>
<item>mn</item>
<item>ru</item>
<item>sr</item>
<item>uk</item>
<item>el</item>
<item>hy</item>
<item>iw</item>
<item>ur</item>
<item>ar</item>
<item>fa</item>
<item>ne</item>
<item>mr</item>
<item>hi</item>
<item>bn</item>
<item>pa</item>
<item>gu</item>
<item>ta</item>
<item>te</item>
<item>kn</item>
<item>ml</item>
<item>si</item>
<item>th</item>
<item>lo</item>
<item>my</item>
<item>ka</item>
<item>am</item>
<item>km</item>
<item>zh-CN</item>
<item>zh-TW</item>
<item>zh-HK</item>
<item>ja</item>
<item>ko</item>
</string-array>
<string-array name='languageNames'>
<item>Afrikaans</item>
<item>Azərbaycan</item>
<item>Bahasa Indonesia</item>
<item>Bahasa Malaysia</item>
<item>Català</item>
<item>Čeština</item>
<item>Dansk</item>
<item>Deutsch</item>
<item>Eesti</item>
<item>English (UK)</item>
<item>English (US)</item>
<item>Español (España)</item>
<item>Español (Latinoamérica)</item>
<item>Euskara</item>
<item>Filipino</item>
<item>Français</item>
<item>Français (Canada)</item>
<item>Galego</item>
<item>Hrvatski</item>
<item>IsiZulu</item>
<item>Íslenska</item>
<item>Italiano</item>
<item>Kiswahili</item>
<item>Latviešu valoda</item>
<item>Lietuvių</item>
<item>Magyar</item>
<item>Nederlands</item>
<item>Norsk</item>
<item>Ozbek</item>
<item>Polski</item>
<item>Português</item>
<item>Português (Brasil)</item>
<item>Română</item>
<item>Shqip</item>
<item>Slovenčina</item>
<item>Slovenščina</item>
<item>Suomi</item>
<item>Svenska</item>
<item>Tiếng Việt</item>
<item>Türkçe</item>
<item>Български</item>
<item>Кыргызча</item>
<item>Қазақ Тілі</item>
<item>Македонски</item>
<item>Монгол</item>
<item>Русский</item>
<item>Српски</item>
<item>Українська</item>
<item>Ελληνικά</item>
<item>Հայերեն</item>
<item>עברית</item>
<item>اردو</item>
<item>العربية</item>
<item>فارسی</item>
<item>नेपाली</item>
<item>मराठी</item>
<item>हिन्दी</item>
<item>বাংলা</item>
<item>ਪੰਜਾਬੀ</item>
<item>ગુજરાતી</item>
<item>தமிழ்</item>
<item>తెలుగు</item>
<item>ಕನ್ನಡ</item>
<item>മലയാളം</item>
<item>සිංහල</item>
<item>ภาษาไทย</item>
<item>ລາວ</item>
<item>ဗမာ</item>
<item>ქართული</item>
<item>አማርኛ</item>
<item>ខ្មែរ</item>
<item>中文 (简体)</item>
<item>中文 (繁體)</item>
<item>中文 (香港)</item>
<item>日本語</item>
<item>한국어</item>
</string-array>
</resources>

View File

@@ -1,12 +1,12 @@
<?xml version="1.0" encoding="utf-8"?>
<?xml version='1.0' encoding='utf-8'?>
<resources>
<string name="app_name">NewPipe</string>
<string name="title_videoitem_detail">NewPipe</string>
<string name="nothingFound">Nothing found</string>
<string name="viewSufix">views</string>
<string name="uploadDatePrefix">Uploaded at: </string>
<string name="noPlayerFound">No StreamPlayer found. You may want to install one.</string>
<string name="installStreamPlayer">Install one</string>
<string name="uploadDatePrefix">Uploaded on </string>
<string name="noPlayerFound">No stream player found. You may want to install one.</string>
<string name="installStreamPlayer">Install</string>
<string name="cancel">Cancel</string>
<string name="fdroidVLCurl">https://f-droid.org/repository/browse/?fdfilter=vlc&amp;fdid=org.videolan.vlc</string>
<string name="open_in_browser">Open in browser</string>
@@ -35,7 +35,7 @@
<string name="fdroidKoreUrl">https://f-droid.org/repository/browse/?fdfilter=Kore&amp;fdid=org.xbmc.kore</string>
<string name="showPlayWithKodiTitle">Show \"Play with Kodi\" option</string>
<string name="showPlayWithKodiSummary">Displays an option to play a video via Kodi media center.</string>
<string name="leftHandLayoutTitle">Left hand friendly layout.</string>
<string name="leftPlayButtonTitle">Show play button on the left side.</string>
<string name="playAudio">Audio</string>
<string name="defaultAudioFormatTitle">Default audio format</string>
<string name="webMAudioDescription">WebM - free format</string>
@@ -45,4 +45,9 @@
<item>Video</item>
<item>Audio</item>
</string-array>
</resources>
<string name="nextVideoTitle">Next video</string>
<string name="showNextAndSimilarTitle">Show next and similar videos</string>
<string name="urlNotSupportedText">URL not supported.</string>
<string name="showSimilarVideosButtonText">Similar videos</string>
<string name="searchLanguageTitle">Preferable content language</string>
</resources>

View File

@@ -36,7 +36,7 @@
<CheckBoxPreference
android:key="@string/leftHandLayout"
android:title="@string/leftHandLayoutTitle"
android:title="@string/leftPlayButtonTitle"
android:defaultValue="false" />
<ListPreference
@@ -46,4 +46,16 @@
android:entryValues="@array/audioFormatList"
android:defaultValue="@string/defaultAudioFormat"/>
<CheckBoxPreference
android:key="@string/showNextVideo"
android:title="@string/showNextAndSimilarTitle"
android:defaultValue="true" />
<ListPreference
android:key="@string/searchLanguage"
android:title="@string/searchLanguageTitle"
android:entries="@array/languageNames"
android:entryValues="@array/languageCodes"
android:defaultValue="en" />
</PreferenceScreen>

394
assets/new_pipe_icon_3.svg Normal file
View File

@@ -0,0 +1,394 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg
xmlns:dc="http://purl.org/dc/elements/1.1/"
xmlns:cc="http://creativecommons.org/ns#"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:svg="http://www.w3.org/2000/svg"
xmlns="http://www.w3.org/2000/svg"
xmlns:xlink="http://www.w3.org/1999/xlink"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
version="1.1"
id="svg2"
viewBox="0 0 192 192"
height="192"
width="192"
inkscape:version="0.91 r13725"
sodipodi:docname="new_pipe_icon_3.svg"
inkscape:export-filename="/home/the-scrabi/Projects/NewPipe/app/src/main/res/mipmap-xxhdpi/ic_launcher.png"
inkscape:export-xdpi="67.5"
inkscape:export-ydpi="67.5">
<sodipodi:namedview
pagecolor="#ffffff"
bordercolor="#666666"
borderopacity="1"
objecttolerance="10"
gridtolerance="10"
guidetolerance="10"
inkscape:pageopacity="0"
inkscape:pageshadow="2"
inkscape:window-width="1920"
inkscape:window-height="1016"
id="namedview4149"
showgrid="false"
inkscape:zoom="2.7234659"
inkscape:cx="82.753124"
inkscape:cy="90.951077"
inkscape:window-x="0"
inkscape:window-y="27"
inkscape:window-maximized="1"
inkscape:current-layer="svg2" />
<defs
id="defs4">
<linearGradient
inkscape:collect="always"
id="linearGradient4924">
<stop
style="stop-color:#ffffff;stop-opacity:0.12857144"
offset="0"
id="stop4926" />
<stop
style="stop-color:#ffffff;stop-opacity:0"
offset="1"
id="stop4928" />
</linearGradient>
<filter
style="color-interpolation-filters:sRGB"
inkscape:label="Drop Shadow"
id="filter4454"
width="1.4"
height="1.4"
x="-0.2"
y="-0.2">
<feFlood
flood-opacity="0.427451"
flood-color="rgb(0,0,0)"
result="flood"
id="feFlood4456" />
<feComposite
in="flood"
in2="SourceGraphic"
operator="in"
result="composite1"
id="feComposite4458" />
<feGaussianBlur
in="composite1"
stdDeviation="10.9"
result="blur"
id="feGaussianBlur4460" />
<feOffset
dx="0"
dy="7"
result="offset"
id="feOffset4462" />
<feComposite
in="SourceGraphic"
in2="offset"
operator="over"
result="composite2"
id="feComposite4464" />
</filter>
<filter
style="color-interpolation-filters:sRGB;"
inkscape:label="Drop Shadow"
id="filter4777">
<feFlood
flood-opacity="0.498039"
flood-color="rgb(0,0,0)"
result="flood"
id="feFlood4779" />
<feComposite
in="flood"
in2="SourceGraphic"
operator="in"
result="composite1"
id="feComposite4781" />
<feGaussianBlur
in="composite1"
stdDeviation="5.82011"
result="blur"
id="feGaussianBlur4783" />
<feOffset
dx="0"
dy="5.6"
result="offset"
id="feOffset4785" />
<feComposite
in="SourceGraphic"
in2="offset"
operator="over"
result="fbSourceGraphic"
id="feComposite4787" />
<feColorMatrix
result="fbSourceGraphicAlpha"
in="fbSourceGraphic"
values="0 0 0 -1 0 0 0 0 -1 0 0 0 0 -1 0 0 0 0 1 0"
id="feColorMatrix4789" />
<feFlood
id="feFlood4791"
flood-opacity="0.498039"
flood-color="rgb(0,0,0)"
result="flood"
in="fbSourceGraphic" />
<feComposite
id="feComposite4793"
in2="fbSourceGraphic"
in="flood"
operator="in"
result="composite1" />
<feGaussianBlur
id="feGaussianBlur4795"
in="composite1"
stdDeviation="5.8"
result="blur" />
<feOffset
id="feOffset4797"
dx="0"
dy="5.6"
result="offset" />
<feComposite
id="feComposite4799"
in2="offset"
in="fbSourceGraphic"
operator="over"
result="composite2" />
</filter>
<filter
style="color-interpolation-filters:sRGB;"
inkscape:label="Drop Shadow"
id="filter4885">
<feFlood
flood-opacity="0.498039"
flood-color="rgb(0,0,0)"
result="flood"
id="feFlood4887" />
<feComposite
in="flood"
in2="SourceGraphic"
operator="in"
result="composite1"
id="feComposite4889" />
<feGaussianBlur
in="composite1"
stdDeviation="7.9"
result="blur"
id="feGaussianBlur4891" />
<feOffset
dx="0"
dy="2.54709"
result="offset"
id="feOffset4893" />
<feComposite
in="SourceGraphic"
in2="offset"
operator="over"
result="fbSourceGraphic"
id="feComposite4895" />
<feColorMatrix
result="fbSourceGraphicAlpha"
in="fbSourceGraphic"
values="0 0 0 -1 0 0 0 0 -1 0 0 0 0 -1 0 0 0 0 1 0"
id="feColorMatrix4897" />
<feFlood
id="feFlood4899"
flood-opacity="0.498039"
flood-color="rgb(0,0,0)"
result="flood"
in="fbSourceGraphic" />
<feComposite
id="feComposite4901"
in2="fbSourceGraphic"
in="flood"
operator="in"
result="composite1" />
<feGaussianBlur
id="feGaussianBlur4903"
in="composite1"
stdDeviation="7.9"
result="blur" />
<feOffset
id="feOffset4905"
dx="0"
dy="2.5"
result="offset" />
<feComposite
id="feComposite4907"
in2="offset"
in="fbSourceGraphic"
operator="over"
result="composite2" />
</filter>
<radialGradient
inkscape:collect="always"
xlink:href="#linearGradient4924"
id="radialGradient4930"
cx="-17.308468"
cy="44.131577"
fx="-17.308468"
fy="44.131577"
r="88"
gradientTransform="matrix(0.00918061,2.1580507,-2.1734097,0.00924596,96.458612,37.749457)"
gradientUnits="userSpaceOnUse" />
<filter
style="color-interpolation-filters:sRGB;"
inkscape:label="Drop Shadow"
id="filter4257">
<feFlood
flood-opacity="0.498039"
flood-color="rgb(0,0,0)"
result="flood"
id="feFlood4259" />
<feComposite
in="flood"
in2="SourceGraphic"
operator="in"
result="composite1"
id="feComposite4261" />
<feGaussianBlur
in="composite1"
stdDeviation="7.9"
result="blur"
id="feGaussianBlur4263" />
<feOffset
dx="0"
dy="5.02645"
result="offset"
id="feOffset4265" />
<feComposite
in="SourceGraphic"
in2="offset"
operator="over"
result="fbSourceGraphic"
id="feComposite4267" />
<feColorMatrix
result="fbSourceGraphicAlpha"
in="fbSourceGraphic"
values="0 0 0 -1 0 0 0 0 -1 0 0 0 0 -1 0 0 0 0 1 0"
id="feColorMatrix4269" />
<feFlood
id="feFlood4271"
flood-opacity="0.498039"
flood-color="rgb(0,0,0)"
result="flood"
in="fbSourceGraphic" />
<feComposite
id="feComposite4273"
in2="fbSourceGraphic"
in="flood"
operator="in"
result="composite1" />
<feGaussianBlur
id="feGaussianBlur4275"
in="composite1"
stdDeviation="7.9"
result="blur" />
<feOffset
id="feOffset4277"
dx="0"
dy="5"
result="offset" />
<feComposite
id="feComposite4279"
in2="offset"
in="fbSourceGraphic"
operator="over"
result="composite2" />
</filter>
<filter
style="color-interpolation-filters:sRGB;"
inkscape:label="Drop Shadow"
id="filter4192">
<feFlood
flood-opacity="0.498039"
flood-color="rgb(0,0,0)"
result="flood"
id="feFlood4194" />
<feComposite
in="flood"
in2="SourceGraphic"
operator="in"
result="composite1"
id="feComposite4196" />
<feGaussianBlur
in="composite1"
stdDeviation="7.7"
result="blur"
id="feGaussianBlur4198" />
<feOffset
dx="0"
dy="5"
result="offset"
id="feOffset4200" />
<feComposite
in="SourceGraphic"
in2="offset"
operator="over"
result="composite2"
id="feComposite4202" />
</filter>
</defs>
<metadata
id="metadata7">
<rdf:RDF>
<cc:Work
rdf:about="">
<dc:format>image/svg+xml</dc:format>
<dc:type
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
<dc:title />
</cc:Work>
</rdf:RDF>
</metadata>
<g
id="g4191"
transform="matrix(1.0909091,0,0,1.0909091,-8.7272727,-8.6363651)">
<rect
ry="20.886885"
y="31"
x="8"
height="128"
width="176"
id="rect4138-6"
style="fill:#747474;fill-opacity:1;stroke:none;stroke-width:17.10300064;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" />
<rect
ry="20.886885"
y="33"
x="8"
height="128"
width="176"
id="rect4138-9"
style="fill:#4d4d4d;fill-opacity:1;stroke:none;stroke-width:17.10300064;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" />
<rect
ry="20.886885"
y="32"
x="8"
height="128"
width="176"
id="rect4138"
style="fill:#616161;fill-opacity:1;stroke:none;stroke-width:17.10300064;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" />
<g
style="filter:url(#filter4192)"
transform="matrix(0.57861531,0,0,0.57861531,-50.721139,-52.115781)"
id="g4141">
<circle
style="fill:#ff5252;fill-opacity:1;stroke:none;stroke-width:17.10300064;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
id="path4144"
cx="255.30112"
cy="257.71143"
r="86.413193" />
<path
inkscape:connector-curvature="0"
style="fill:#ffffff;fill-opacity:1;stroke:none;stroke-width:17.10300064;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
id="path4140"
d="m 303.94425,257.77663 -40.45827,23.31512 -40.45827,23.31511 0.0377,-46.69544 0.0377,-46.69545 40.42062,23.38033 z" />
</g>
<rect
ry="21.213242"
y="31"
x="8"
height="130"
width="176"
id="rect4138-8"
style="fill:url(#radialGradient4930);fill-opacity:1;stroke:none;stroke-width:17.10300064;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" />
</g>
</svg>

After

Width:  |  Height:  |  Size: 11 KiB

526
assets/new_pipe_icon_4.svg Normal file
View File

@@ -0,0 +1,526 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg
xmlns:dc="http://purl.org/dc/elements/1.1/"
xmlns:cc="http://creativecommons.org/ns#"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:svg="http://www.w3.org/2000/svg"
xmlns="http://www.w3.org/2000/svg"
xmlns:xlink="http://www.w3.org/1999/xlink"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
version="1.1"
id="svg2"
viewBox="0 0 192 192"
height="192"
width="192"
inkscape:version="0.91 r13725"
sodipodi:docname="new_pipe_icon_4.svg"
inkscape:export-filename="/home/the-scrabi/Projects/NewPipe/app/src/main/res/mipmap-xxhdpi/ic_launcher.png"
inkscape:export-xdpi="67.5"
inkscape:export-ydpi="67.5">
<sodipodi:namedview
pagecolor="#ffffff"
bordercolor="#666666"
borderopacity="1"
objecttolerance="10"
gridtolerance="10"
guidetolerance="10"
inkscape:pageopacity="0"
inkscape:pageshadow="2"
inkscape:window-width="1920"
inkscape:window-height="1016"
id="namedview4149"
showgrid="false"
inkscape:zoom="2.2793069"
inkscape:cx="74.912287"
inkscape:cy="82.673449"
inkscape:window-x="0"
inkscape:window-y="27"
inkscape:window-maximized="1"
inkscape:current-layer="svg2" />
<defs
id="defs4">
<linearGradient
inkscape:collect="always"
id="linearGradient4447">
<stop
style="stop-color:#ffffff;stop-opacity:0.1"
offset="0"
id="stop4449" />
<stop
style="stop-color:#ffffff;stop-opacity:0"
offset="1"
id="stop4451" />
</linearGradient>
<filter
style="color-interpolation-filters:sRGB"
inkscape:label="Drop Shadow"
id="filter4454"
width="1.4"
height="1.4"
x="-0.2"
y="-0.2">
<feFlood
flood-opacity="0.427451"
flood-color="rgb(0,0,0)"
result="flood"
id="feFlood4456" />
<feComposite
in="flood"
in2="SourceGraphic"
operator="in"
result="composite1"
id="feComposite4458" />
<feGaussianBlur
in="composite1"
stdDeviation="10.9"
result="blur"
id="feGaussianBlur4460" />
<feOffset
dx="0"
dy="7"
result="offset"
id="feOffset4462" />
<feComposite
in="SourceGraphic"
in2="offset"
operator="over"
result="composite2"
id="feComposite4464" />
</filter>
<filter
style="color-interpolation-filters:sRGB;"
inkscape:label="Drop Shadow"
id="filter4777">
<feFlood
flood-opacity="0.498039"
flood-color="rgb(0,0,0)"
result="flood"
id="feFlood4779" />
<feComposite
in="flood"
in2="SourceGraphic"
operator="in"
result="composite1"
id="feComposite4781" />
<feGaussianBlur
in="composite1"
stdDeviation="5.82011"
result="blur"
id="feGaussianBlur4783" />
<feOffset
dx="0"
dy="5.6"
result="offset"
id="feOffset4785" />
<feComposite
in="SourceGraphic"
in2="offset"
operator="over"
result="fbSourceGraphic"
id="feComposite4787" />
<feColorMatrix
result="fbSourceGraphicAlpha"
in="fbSourceGraphic"
values="0 0 0 -1 0 0 0 0 -1 0 0 0 0 -1 0 0 0 0 1 0"
id="feColorMatrix4789" />
<feFlood
id="feFlood4791"
flood-opacity="0.498039"
flood-color="rgb(0,0,0)"
result="flood"
in="fbSourceGraphic" />
<feComposite
id="feComposite4793"
in2="fbSourceGraphic"
in="flood"
operator="in"
result="composite1" />
<feGaussianBlur
id="feGaussianBlur4795"
in="composite1"
stdDeviation="5.8"
result="blur" />
<feOffset
id="feOffset4797"
dx="0"
dy="5.6"
result="offset" />
<feComposite
id="feComposite4799"
in2="offset"
in="fbSourceGraphic"
operator="over"
result="composite2" />
</filter>
<filter
style="color-interpolation-filters:sRGB;"
inkscape:label="Drop Shadow"
id="filter4885">
<feFlood
flood-opacity="0.498039"
flood-color="rgb(0,0,0)"
result="flood"
id="feFlood4887" />
<feComposite
in="flood"
in2="SourceGraphic"
operator="in"
result="composite1"
id="feComposite4889" />
<feGaussianBlur
in="composite1"
stdDeviation="7.9"
result="blur"
id="feGaussianBlur4891" />
<feOffset
dx="0"
dy="2.54709"
result="offset"
id="feOffset4893" />
<feComposite
in="SourceGraphic"
in2="offset"
operator="over"
result="fbSourceGraphic"
id="feComposite4895" />
<feColorMatrix
result="fbSourceGraphicAlpha"
in="fbSourceGraphic"
values="0 0 0 -1 0 0 0 0 -1 0 0 0 0 -1 0 0 0 0 1 0"
id="feColorMatrix4897" />
<feFlood
id="feFlood4899"
flood-opacity="0.498039"
flood-color="rgb(0,0,0)"
result="flood"
in="fbSourceGraphic" />
<feComposite
id="feComposite4901"
in2="fbSourceGraphic"
in="flood"
operator="in"
result="composite1" />
<feGaussianBlur
id="feGaussianBlur4903"
in="composite1"
stdDeviation="7.9"
result="blur" />
<feOffset
id="feOffset4905"
dx="0"
dy="2.5"
result="offset" />
<feComposite
id="feComposite4907"
in2="offset"
in="fbSourceGraphic"
operator="over"
result="composite2" />
</filter>
<filter
style="color-interpolation-filters:sRGB;"
inkscape:label="Drop Shadow"
id="filter4257">
<feFlood
flood-opacity="0.498039"
flood-color="rgb(0,0,0)"
result="flood"
id="feFlood4259" />
<feComposite
in="flood"
in2="SourceGraphic"
operator="in"
result="composite1"
id="feComposite4261" />
<feGaussianBlur
in="composite1"
stdDeviation="7.9"
result="blur"
id="feGaussianBlur4263" />
<feOffset
dx="0"
dy="5.02645"
result="offset"
id="feOffset4265" />
<feComposite
in="SourceGraphic"
in2="offset"
operator="over"
result="fbSourceGraphic"
id="feComposite4267" />
<feColorMatrix
result="fbSourceGraphicAlpha"
in="fbSourceGraphic"
values="0 0 0 -1 0 0 0 0 -1 0 0 0 0 -1 0 0 0 0 1 0"
id="feColorMatrix4269" />
<feFlood
id="feFlood4271"
flood-opacity="0.498039"
flood-color="rgb(0,0,0)"
result="flood"
in="fbSourceGraphic" />
<feComposite
id="feComposite4273"
in2="fbSourceGraphic"
in="flood"
operator="in"
result="composite1" />
<feGaussianBlur
id="feGaussianBlur4275"
in="composite1"
stdDeviation="7.9"
result="blur" />
<feOffset
id="feOffset4277"
dx="0"
dy="5"
result="offset" />
<feComposite
id="feComposite4279"
in2="offset"
in="fbSourceGraphic"
operator="over"
result="composite2" />
</filter>
<filter
style="color-interpolation-filters:sRGB;"
inkscape:label="Drop Shadow"
id="filter4192">
<feFlood
flood-opacity="0.498039"
flood-color="rgb(0,0,0)"
result="flood"
id="feFlood4194" />
<feComposite
in="flood"
in2="SourceGraphic"
operator="in"
result="composite1"
id="feComposite4196" />
<feGaussianBlur
in="composite1"
stdDeviation="7.7"
result="blur"
id="feGaussianBlur4198" />
<feOffset
dx="0"
dy="5"
result="offset"
id="feOffset4200" />
<feComposite
in="SourceGraphic"
in2="offset"
operator="over"
result="composite2"
id="feComposite4202" />
</filter>
<filter
style="color-interpolation-filters:sRGB;"
inkscape:label="Drop Shadow"
id="filter4349">
<feFlood
flood-opacity="0.498039"
flood-color="rgb(0,0,0)"
result="flood"
id="feFlood4351" />
<feComposite
in="flood"
in2="SourceGraphic"
operator="in"
result="composite1"
id="feComposite4353" />
<feGaussianBlur
in="composite1"
stdDeviation="7.2"
result="blur"
id="feGaussianBlur4355" />
<feOffset
dx="0"
dy="5"
result="offset"
id="feOffset4357" />
<feComposite
in="SourceGraphic"
in2="offset"
operator="over"
result="composite2"
id="feComposite4359" />
</filter>
<filter
style="color-interpolation-filters:sRGB;"
inkscape:label="Drop Shadow"
id="filter4361">
<feFlood
flood-opacity="0.498039"
flood-color="rgb(0,0,0)"
result="flood"
id="feFlood4363" />
<feComposite
in="flood"
in2="SourceGraphic"
operator="in"
result="composite1"
id="feComposite4365" />
<feGaussianBlur
in="composite1"
stdDeviation="5.3"
result="blur"
id="feGaussianBlur4367" />
<feOffset
dx="0"
dy="5"
result="offset"
id="feOffset4369" />
<feComposite
in="SourceGraphic"
in2="offset"
operator="over"
result="composite2"
id="feComposite4371" />
</filter>
<filter
style="color-interpolation-filters:sRGB;"
inkscape:label="Drop Shadow"
id="filter4481">
<feFlood
flood-opacity="0.498039"
flood-color="rgb(0,0,0)"
result="flood"
id="feFlood4483" />
<feComposite
in="flood"
in2="SourceGraphic"
operator="in"
result="composite1"
id="feComposite4485" />
<feGaussianBlur
in="composite1"
stdDeviation="5"
result="blur"
id="feGaussianBlur4487" />
<feOffset
dx="0"
dy="5"
result="offset"
id="feOffset4489" />
<feComposite
in="SourceGraphic"
in2="offset"
operator="over"
result="composite2"
id="feComposite4491" />
</filter>
<filter
style="color-interpolation-filters:sRGB;"
inkscape:label="Drop Shadow"
id="filter4433">
<feFlood
flood-opacity="0.2"
flood-color="rgb(0,0,0)"
result="flood"
id="feFlood4435" />
<feComposite
in="flood"
in2="SourceGraphic"
operator="in"
result="composite1"
id="feComposite4437" />
<feGaussianBlur
in="composite1"
stdDeviation="4"
result="blur"
id="feGaussianBlur4439" />
<feOffset
dx="0"
dy="4"
result="offset"
id="feOffset4441" />
<feComposite
in="SourceGraphic"
in2="offset"
operator="over"
result="composite2"
id="feComposite4443" />
</filter>
<radialGradient
inkscape:collect="always"
xlink:href="#linearGradient4447"
id="radialGradient4453"
cx="0.56012386"
cy="0.35701406"
fx="0.56012386"
fy="0.35701406"
r="88"
gradientUnits="userSpaceOnUse"
gradientTransform="matrix(0.00132321,2.1587518,-2.1815784,0.00133718,1.1350038,-0.41402508)" />
</defs>
<metadata
id="metadata7">
<rdf:RDF>
<cc:Work
rdf:about="">
<dc:format>image/svg+xml</dc:format>
<dc:type
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
<dc:title></dc:title>
</cc:Work>
</rdf:RDF>
</metadata>
<path
style="opacity:1;fill:#ff7575;fill-opacity:1;stroke:none;stroke-width:17.10300064;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
id="path4144-9"
r="88"
cy="104"
cx="88" />
<circle
style="fill:#ff5252;fill-opacity:1;stroke:none;stroke-width:17.10300064;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
id="path4144-6"
cx="88"
cy="104"
r="0" />
<g
id="g4455">
<g
style="filter:url(#filter4433)"
transform="translate(8,-8)"
id="g4416">
<circle
style="opacity:1;fill:#ff7575;fill-opacity:1;stroke:none;stroke-width:17.10300064;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
id="path4144-67"
cx="88"
cy="104"
r="88" />
<path
style="opacity:1;fill:#cc4242;fill-opacity:1;stroke:none;stroke-width:17.10300064;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
id="path4144-5"
sodipodi:type="arc"
sodipodi:cx="88"
sodipodi:cy="104"
sodipodi:rx="88"
sodipodi:ry="88"
sodipodi:start="0"
sodipodi:end="3.1387981"
sodipodi:open="true"
d="M 176,104 A 88,88 0 0 1 88.12296,191.99991 88,88 0 0 1 3.4361909e-4,104.24592" />
<ellipse
style="fill:#ff5252;fill-opacity:1;stroke:none;stroke-width:17.10300064;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
id="path4144"
cx="88"
cy="104"
rx="88"
ry="87" />
<path
inkscape:connector-curvature="0"
style="fill:#ffffff;fill-opacity:1;stroke:none;stroke-width:17.10300064;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
id="path4140"
d="M 137.53637,104.0664 96.33516,127.80966 55.133958,151.5529 55.17235,104 55.210742,56.447076 96.37361,80.256739 Z" />
</g>
<circle
r="88"
cy="96"
cx="96"
id="path4445"
style="opacity:1;fill:url(#radialGradient4453);fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:3.5;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" />
</g>
</svg>

After

Width:  |  Height:  |  Size: 14 KiB