Merge branch 'develop'

This commit is contained in:
daniel oeh 2014-02-27 21:10:38 +01:00
commit ffb0fdcb12
116 changed files with 7653 additions and 3580 deletions

View File

@ -15,6 +15,7 @@ trans.fr = res/values-fr/strings.xml
trans.hi_IN = res/values-hi-rIN/strings.xml
trans.it_IT = res/values-it-rIT/strings.xml
trans.nl = res/values-nl/strings.xml
trans.pl_PL = res/values-pl-rPL/strings.xml
trans.pt = res/values-pt/strings.xml
trans.pt_BR = res/values-pt-rBR/strings.xml
trans.ro_RO = res/values-ro-rRO/strings.xml

View File

@ -1,15 +1,16 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="de.danoeh.antennapod"
android:versionCode="33"
android:versionName="0.9.8.0" >
android:versionCode="34"
android:versionName="0.9.8.1" >
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.WAKE_LOCK" />
<uses-sdk
android:minSdkVersion="10"
android:targetSdkVersion="18" />
android:targetSdkVersion="19" />
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
@ -153,7 +154,7 @@
android:name=".service.download.DownloadService"
android:enabled="true"/>
<service
android:name="de.danoeh.antennapod.service.PlaybackService"
android:name=".service.playback.PlaybackService"
android:enabled="true"
android:exported="true">
</service>
@ -194,7 +195,7 @@
</activity>
<service
android:name=".service.PlayerWidgetService"
android:name=".service.playback.PlayerWidgetService"
android:enabled="true"
android:exported="false">
</service>
@ -327,7 +328,7 @@
android:configChanges="keyboardHidden|orientation">
<meta-data
android:name="android.support.PARENT_ACTIVITY"
android:value="de.danoeh.antennapod.activity.MiroGuideMainActiviy" />
android:value="de.danoeh.antennapod.activity.MiroGuideMainActivity" />
</activity>
<activity
android:name=".activity.MiroGuideChannelViewActivity"

View File

@ -1,6 +1,12 @@
Change Log
==========
Version 0.9.8.1
---------------
* Added option to flattr an episode automatically after 80 percent of the episode have been played
* Added Polish translation
* Several bugfixes and improvements
Version 0.9.8.0
---------------
* Added access to the gpodder.net directory

View File

@ -8,6 +8,7 @@ LatinSuD
wseemann
hzulla
andrewgaul
peschmae0
Translations:
@ -28,3 +29,4 @@ Portuguese: smarquespt
Swedish: nilso, Bio, TwoD, bpnilsson
Hindi: siddhusengar
Dutch: e2jk
Polish (Poland): Mephistofeles, shark103, tyle

View File

@ -41,7 +41,7 @@
<div id="header" align="center">
<img src="logo.png" alt="Logo" width="100px" height="100px"/>
<p>AntennaPod, Version 0.9.8.0</p>
<p>AntennaPod, Version 0.9.8.1</p>
<p>Copyright © 2012 Daniel Oeh</p>

View File

@ -3,7 +3,7 @@ buildscript {
mavenCentral()
}
dependencies {
classpath 'com.android.tools.build:gradle:0.6.+'
classpath 'com.android.tools.build:gradle:0.8.+'
}
}
apply plugin: 'android'
@ -23,9 +23,9 @@ dependencies {
new URL('http://www.aocate.com/presto/client/presto_client-0.8.5.jar').withInputStream{ i -> prestoLib.withOutputStream{ it << i }}
}
compile 'com.android.support:appcompat-v7:18.0.+'
compile 'org.apache.commons:commons-lang3:3.1'
compile ('org.shredzone.flattr4j:flattr4j-core:2.7') {
compile 'com.android.support:appcompat-v7:19.0.+'
compile 'org.apache.commons:commons-lang3:3.2.1'
compile ('org.shredzone.flattr4j:flattr4j-core:2.8') {
exclude group: 'org.apache.httpcomponents', module: 'httpcore'
exclude group: 'org.apache.httpcomponents', module: 'httpclient'
exclude group: 'org.json', module: 'json'
@ -37,12 +37,12 @@ dependencies {
}
android {
compileSdkVersion 18
buildToolsVersion "18.1.0"
compileSdkVersion 19
buildToolsVersion "19.0.1"
defaultConfig {
minSdkVersion 10
targetSdkVersion 18
targetSdkVersion 19
testPackageName "de.test.antennapod"
testInstrumentationRunner "instrumentationTest.de.test.antennapod.AntennaPodTestRunner"
}
@ -94,4 +94,9 @@ android {
signingConfig signingConfigs.releaseConfig
}
}
packagingOptions {
exclude 'META-INF/LICENSE.txt'
exclude 'META-INF/NOTICE.txt'
}
}

21
pom.xml
View File

@ -5,7 +5,7 @@
<groupId>de.danoeh</groupId>
<artifactId>antennapod</artifactId>
<packaging>apk</packaging>
<version>0.9.8.0</version>
<version>0.9.8.1</version>
<name>AntennaPod</name>
@ -13,18 +13,18 @@
<dependency>
<groupId>android.support</groupId>
<artifactId>compatibility-v4</artifactId>
<version>18</version>
<version>19</version>
</dependency>
<dependency>
<groupId>android.support</groupId>
<artifactId>compatibility-v7-appcompat</artifactId>
<version>18</version>
<version>19</version>
<type>apklib</type>
</dependency>
<dependency>
<groupId>android.support</groupId>
<artifactId>compatibility-v7-appcompat</artifactId>
<version>18</version>
<version>19</version>
<type>jar</type>
</dependency>
<dependency>
@ -58,15 +58,16 @@
</exclusions>
</dependency>
<dependency>
<groupId>com.google.android</groupId>
<groupId>android</groupId>
<artifactId>android</artifactId>
<scope>provided</scope>
<version>4.1.1.4</version>
<version>4.4_r1</version>
</dependency>
<dependency>
<groupId>com.google.android</groupId>
<groupId>com.google.android.annotations</groupId>
<artifactId>annotations</artifactId>
<version>4.1.1.4</version>
<version>22.3</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>commons-io</groupId>
@ -109,11 +110,11 @@
<plugin>
<groupId>com.jayway.maven.plugins.android.generation2</groupId>
<artifactId>android-maven-plugin</artifactId>
<version>3.6.1</version>
<version>3.8.0</version>
<configuration>
<sdk>
<path>${env.ANDROID_HOME}</path>
<platform>18</platform>
<platform>19</platform>
</sdk>
<manifest>
<debuggable>true</debuggable>

View File

@ -9,7 +9,6 @@
# Project target.
proguard.config=proguard.cfg
target=android-18
android.library.reference.1=submodules/ActionBarSherlock/library
android.library.reference.2=submodules/ViewPagerIndicator/library
android.library.reference.3=submodules/dslv/library
target=android-19
android.library.reference.1=submodules/dslv/library

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 10 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 21 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 23 KiB

View File

@ -0,0 +1,10 @@
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android"
android:shape="rectangle">
<gradient
android:type="radial"
android:gradientRadius="60"
android:startColor="#000000"
android:endColor="@android:color/transparent"/>
</shape>

View File

@ -26,6 +26,7 @@
<ImageButton
android:id="@+id/butNavLeft"
android:contentDescription="@string/show_shownotes_label"
android:layout_width="60dp"
android:layout_height="match_parent"
android:layout_alignParentLeft="true"
@ -34,6 +35,7 @@
<ImageButton
android:id="@+id/butNavRight"
android:contentDescription="@string/show_chapters_label"
android:layout_width="60dp"
android:layout_height="match_parent"
android:layout_alignParentRight="true"
@ -89,6 +91,7 @@
<ImageButton
android:id="@+id/butPlay"
android:contentDescription="@string/pause_label"
android:layout_width="80dp"
android:layout_height="match_parent"
android:layout_centerHorizontal="true"
@ -97,6 +100,7 @@
<ImageButton
android:id="@+id/butRev"
android:contentDescription="@string/rewind_label"
android:layout_width="60dp"
android:layout_height="match_parent"
android:layout_toLeftOf="@id/butPlay"
@ -105,6 +109,7 @@
<ImageButton
android:id="@+id/butFF"
android:contentDescription="@string/fast_forward_label"
android:layout_width="60dp"
android:layout_height="match_parent"
android:layout_toRightOf="@id/butPlay"

View File

@ -1,13 +1,14 @@
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical" >
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<VideoView
<de.danoeh.antennapod.view.AspectRatioVideoView
android:id="@+id/videoview"
android:layout_width="match_parent"
android:layout_height="match_parent" />
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"/>
<ProgressBar
android:id="@+id/progressIndicator"
@ -15,58 +16,32 @@
android:layout_height="wrap_content"
android:layout_gravity="center"
android:visibility="invisible"
android:indeterminateOnly="true" />
<!-- Mediaplayer controls -->
android:indeterminateOnly="true"/>
<ImageButton
android:id="@+id/butPlay"
android:contentDescription="@string/pause_label"
android:layout_width="80dp"
android:layout_height="80dp"
android:layout_gravity="center"
android:scaleType="fitXY"
android:background="@drawable/overlay_button_circle_background"
android:src="@drawable/ic_action_pause_over_video"/>
<LinearLayout
android:id="@+id/overlay"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_gravity="bottom|center"
android:background="?attr/overlay_background"
android:orientation="vertical" >
<RelativeLayout
android:id="@+id/playercontrol"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:layout_gravity="clip_horizontal"
android:layout_margin="4dp" >
<ImageButton
android:id="@+id/butPlay"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerHorizontal="true"
android:background="?attr/borderless_button"
android:src="?attr/av_pause" />
<ImageButton
android:id="@+id/butFF"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentTop="true"
android:layout_marginLeft="8dp"
android:layout_toRightOf="@+id/butPlay"
android:background="?attr/borderless_button"
android:src="?attr/av_fast_forward" />
<ImageButton
android:id="@+id/butRev"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentTop="true"
android:layout_marginRight="8dp"
android:layout_toLeftOf="@+id/butPlay"
android:background="?attr/borderless_button"
android:src="?attr/av_rewind" />
</RelativeLayout>
android:background="#80000000"
android:orientation="vertical">
<RelativeLayout
android:id="@+id/timecontrol"
android:layout_width="match_parent"
android:layout_height="30dp"
android:layout_marginBottom="4dp" >
android:layout_height="50dp"
android:paddingTop="8dp"
android:layout_marginBottom="4dp">
<TextView
android:id="@+id/txtvPosition"
@ -77,7 +52,10 @@
android:layout_marginBottom="8dp"
android:layout_marginLeft="8dp"
android:layout_marginRight="8dp"
android:text="@string/position_default_label" />
android:layout_marginTop="4dp"
android:textColor="@color/white"
android:textStyle="bold"
android:text="@string/position_default_label"/>
<TextView
android:id="@+id/txtvLength"
@ -88,7 +66,10 @@
android:layout_marginBottom="8dp"
android:layout_marginLeft="8dp"
android:layout_marginRight="8dp"
android:text="@string/position_default_label" />
android:layout_marginTop="4dp"
android:textColor="@color/white"
android:textStyle="bold"
android:text="@string/position_default_label"/>
<SeekBar
android:id="@+id/sbPosition"
@ -96,7 +77,7 @@
android:layout_height="wrap_content"
android:layout_toLeftOf="@+id/txtvLength"
android:layout_toRightOf="@+id/txtvPosition"
android:max="500" />
android:max="500"/>
</RelativeLayout>
</LinearLayout>

View File

@ -56,6 +56,7 @@
<ImageButton
android:id="@+id/butNavUp"
android:contentDescription="@string/navigate_upwards_label"
android:layout_width="60dp"
android:layout_height="60dp"
android:layout_alignParentLeft="true"

View File

@ -13,6 +13,7 @@
<ImageButton
android:id="@+id/butNavLeft"
android:contentDescription="@string/show_shownotes_label"
android:layout_width="60dp"
android:layout_height="match_parent"
android:layout_alignParentLeft="true"
@ -21,6 +22,7 @@
<ImageButton
android:id="@+id/butNavRight"
android:contentDescription="@string/show_chapters_label"
android:layout_width="60dp"
android:layout_height="match_parent"
android:layout_alignParentRight="true"
@ -76,6 +78,7 @@
<ImageButton
android:id="@+id/butPlay"
android:contentDescription="@string/pause_label"
android:layout_width="80dp"
android:layout_height="match_parent"
android:layout_centerHorizontal="true"
@ -84,6 +87,7 @@
<ImageButton
android:id="@+id/butRev"
android:contentDescription="@string/rewind_label"
android:layout_width="80dp"
android:layout_height="match_parent"
android:layout_toLeftOf="@id/butPlay"
@ -92,6 +96,7 @@
<ImageButton
android:id="@+id/butFF"
android:contentDescription="@string/fast_forward_label"
android:layout_width="80dp"
android:layout_height="match_parent"
android:layout_toRightOf="@id/butPlay"
@ -100,6 +105,7 @@
<Button
android:id="@+id/butPlaybackSpeed"
android:contentDescription="@string/set_playback_speed_label"
android:layout_width="80dp"
android:layout_height="match_parent"
android:layout_toRightOf="@id/butFF"

View File

@ -7,6 +7,7 @@
<ImageView
android:id="@+id/imgvCover"
android:contentDescription="@string/cover_label"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_gravity="center"

View File

@ -1,6 +1,7 @@
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="0dip"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="0dip"
android:layout_height="match_parent"
android:layout_weight="1"
android:paddingLeft="4dp" >
@ -31,7 +32,8 @@
android:layout_width="@dimen/enc_icons_size"
android:layout_height="@dimen/enc_icons_size"
android:layout_below="@id/txtvPublished"
android:padding="2dp" />
android:padding="2dp"
tools:ignore="ContentDescription"/>
<TextView
android:id="@+id/txtvLenSize"

View File

@ -34,6 +34,7 @@
<ImageButton
android:id="@+id/butNavUp"
android:contentDescription="@string/navigate_upwards_label"
android:layout_width="60dp"
android:layout_height="60dp"
android:layout_alignParentLeft="true"

View File

@ -1,10 +1,12 @@
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent" >
<ImageView
android:id="@+id/imgvFeedimage"
android:contentDescription="@string/cover_label"
android:layout_width="@dimen/thumbnail_length_itemlist"
android:layout_height="@dimen/thumbnail_length_itemlist"
android:layout_alignParentLeft="true"
@ -12,6 +14,7 @@
<ImageButton
android:id="@+id/butAction"
android:contentDescription="@string/butAction_label"
android:layout_width="48dp"
android:layout_height="match_parent"
android:layout_alignParentBottom="true"
@ -83,7 +86,8 @@
android:layout_height="@dimen/enc_icons_size"
android:layout_alignParentRight="true"
android:layout_marginLeft="8dp"
android:layout_marginRight="8dp" />
android:layout_marginRight="8dp"
tools:ignore="ContentDescription"/>
<ProgressBar
android:id="@+id/pbar_episode_progress"
@ -97,6 +101,7 @@
<ImageView
android:id="@+id/statusPlaying"
android:contentDescription="@string/status_playing_label"
android:layout_width="@dimen/status_indicator_width"
android:layout_height="18dp"
android:layout_alignParentRight="true"

View File

@ -24,6 +24,7 @@
<ImageView
android:id="@+id/imgvCover"
android:contentDescription="@string/cover_label"
android:layout_width="@dimen/external_player_height"
android:layout_height="@dimen/external_player_height"
android:layout_alignParentLeft="true"
@ -76,6 +77,7 @@
<ImageButton
android:id="@+id/butPlay"
android:contentDescription="@string/pause_label"
android:layout_width="@dimen/external_player_height"
android:layout_height="@dimen/external_player_height"
android:background="?attr/borderless_button" />

View File

@ -12,6 +12,7 @@
<ImageView
android:id="@+id/imgvCover"
android:contentDescription="@string/cover_label"
android:layout_width="70dp"
android:layout_height="70dp"
android:layout_alignParentLeft="true"
@ -96,22 +97,6 @@
android:layout_toRightOf="@id/center_divider"/>
</RelativeLayout>
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginLeft="8dp"
android:layout_marginBottom="8dp"
android:layout_marginTop="24dp"
android:textSize="@dimen/text_size_medium"
android:textColor="?android:attr/textColorPrimary"
android:text="@string/description_label"/>
<TextView
android:id="@+id/txtvDescription"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_margin="8dp"/>
<TextView
android:id="@+id/txtvSettings"
android:layout_width="match_parent"
@ -132,6 +117,23 @@
android:text="@string/auto_download_label"
android:enabled="false"
android:textColor="?android:attr/textColorPrimary"/>
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginLeft="8dp"
android:layout_marginBottom="8dp"
android:layout_marginTop="24dp"
android:textSize="@dimen/text_size_medium"
android:textColor="?android:attr/textColorPrimary"
android:text="@string/description_label"/>
<TextView
android:id="@+id/txtvDescription"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_margin="8dp"/>
</LinearLayout>
</ScrollView>

View File

@ -27,6 +27,7 @@
<ImageButton
android:id="@+id/butAction"
android:contentDescription="@string/butAction_label"
android:layout_width="48dp"
android:layout_height="match_parent"
android:layout_alignParentBottom="true"

View File

@ -1,6 +1,7 @@
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="0dip"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="0dip"
android:layout_height="match_parent"
android:layout_weight="1"
android:paddingLeft="4dp" >
@ -45,10 +46,12 @@
android:layout_height="@dimen/enc_icons_size"
android:layout_below="@id/txtvPublished"
android:layout_toLeftOf="@+id/imgvInPlaylist"
android:padding="2dp" />
android:padding="2dp"
tools:ignore="ContentDescription"/>
<ImageView
android:id="@id/imgvInPlaylist"
android:contentDescription="@string/in_queue_label"
android:layout_width="@dimen/enc_icons_size"
android:layout_height="@dimen/enc_icons_size"
android:layout_below="@id/txtvPublished"
@ -59,6 +62,7 @@
<ImageView
android:id="@id/imgvDownloaded"
android:contentDescription="@string/status_downloaded_label"
android:layout_width="@dimen/enc_icons_size"
android:layout_height="@dimen/enc_icons_size"
android:layout_below="@id/txtvPublished"
@ -69,6 +73,7 @@
<ImageView
android:id="@id/imgvDownloading"
android:contentDescription="@string/downloading_label"
android:layout_width="@dimen/enc_icons_size"
android:layout_height="@dimen/enc_icons_size"
android:layout_below="@id/txtvPublished"
@ -102,6 +107,7 @@
<ImageButton
android:id="@id/butAction"
android:contentDescription="@string/butAction_label"
android:layout_width="48dp"
android:layout_height="match_parent"
android:layout_alignParentBottom="true"
@ -118,6 +124,7 @@
<TextView
android:id="@+id/statusUnread"
android:contentDescription="@string/status_unread_label"
android:layout_width="wrap_content"
android:layout_height="18dp"
android:layout_alignParentRight="true"
@ -134,6 +141,7 @@
<ImageView
android:id="@+id/statusPlaying"
android:contentDescription="@string/status_playing_label"
android:layout_width="@dimen/status_indicator_width"
android:layout_height="18dp"
android:layout_alignParentRight="true"

View File

@ -1,11 +1,13 @@
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:paddingRight="8dp" >
<ImageView
android:id="@+id/imgvFeedimage"
android:contentDescription="@string/cover_label"
android:layout_width="@dimen/thumbnail_length"
android:layout_height="@dimen/thumbnail_length"
android:layout_alignParentLeft="true"
@ -25,6 +27,7 @@
<RelativeLayout
android:id="@+id/lNewStatusLabel"
android:contentDescription="@string/new_episodes_count_label"
android:layout_width="@dimen/status_indicator_width"
android:layout_height="0dip"
android:layout_marginBottom="8dp"
@ -38,7 +41,8 @@
android:layout_alignParentRight="true"
android:layout_centerVertical="true"
android:layout_marginRight="4dp"
android:src="@drawable/white_circle" />
android:src="@drawable/white_circle"
tools:ignore="ContentDescription"/>
<TextView
android:id="@+id/txtvNewEps"
@ -57,6 +61,7 @@
<RelativeLayout
android:id="@+id/lProgressStatusLabel"
android:contentDescription="@string/in_progress_episodes_count_label"
android:layout_width="@dimen/status_indicator_width"
android:layout_height="0dip"
android:layout_marginBottom="8dp"
@ -84,7 +89,8 @@
android:layout_centerVertical="true"
android:layout_marginRight="2dp"
android:layout_marginLeft="2dp"
android:src="@drawable/av_play_dark" />
android:src="@drawable/av_play_dark"
tools:ignore="ContentDescription"/>
</RelativeLayout>
</LinearLayout>

View File

@ -1,10 +1,12 @@
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="wrap_content"
android:layout_height="wrap_content">
<ImageView
android:id="@+id/imgvFeedimage"
android:contentDescription="@string/cover_label"
android:layout_width="@dimen/thumbnail_length"
android:layout_height="@dimen/thumbnail_length"
android:layout_alignParentTop="true"
@ -25,6 +27,7 @@
<RelativeLayout
android:id="@+id/lNewStatusLabel"
android:contentDescription="@string/new_episodes_count_label"
android:layout_width="@dimen/status_indicator_width"
android:layout_height="0dip"
android:layout_marginBottom="8dp"
@ -38,7 +41,8 @@
android:layout_alignParentRight="true"
android:layout_centerVertical="true"
android:layout_marginRight="4dp"
android:src="@drawable/white_circle"/>
android:src="@drawable/white_circle"
tools:ignore="ContentDescription"/>
<TextView
android:id="@+id/txtvNewEps"
@ -57,6 +61,7 @@
<RelativeLayout
android:id="@+id/lProgressStatusLabel"
android:contentDescription="@string/in_progress_episodes_count_label"
android:layout_width="@dimen/status_indicator_width"
android:layout_height="0dip"
android:layout_marginBottom="8dp"
@ -84,7 +89,8 @@
android:layout_centerVertical="true"
android:layout_marginLeft="2dp"
android:layout_marginRight="2dp"
android:src="@drawable/av_play_dark"/>
android:src="@drawable/av_play_dark"
tools:ignore="ContentDescription"/>
</RelativeLayout>
</LinearLayout>

View File

@ -5,6 +5,7 @@
android:layout_height="match_parent">
<ImageView
android:id="@+id/imgvCover"
android:contentDescription="@string/cover_label"
android:layout_width="@dimen/thumbnail_length_itemlist"
android:layout_height="@dimen/thumbnail_length_itemlist"
android:layout_alignParentLeft="true"

View File

@ -6,6 +6,7 @@
<ImageView
android:id="@+id/imgvCover"
android:contentDescription="@string/cover_label"
android:layout_width="@dimen/thumbnail_length_onlinefeedview"
android:layout_height="@dimen/thumbnail_length_onlinefeedview"
android:layout_alignParentLeft="true"

View File

@ -5,6 +5,7 @@
<ImageView
android:id="@+id/imgvFeedimage"
android:contentDescription="@string/cover_label"
android:layout_width="@dimen/thumbnail_length_itemlist"
android:layout_height="@dimen/thumbnail_length_itemlist"
android:layout_alignParentLeft="true"

View File

@ -11,6 +11,7 @@
<ImageButton
android:id="@+id/butPlay"
android:contentDescription="@string/play_label"
android:layout_width="56dp"
android:layout_height="match_parent"
android:layout_alignParentRight="true"

View File

@ -6,6 +6,7 @@
<ImageView
android:id="@+id/imgvFeedimage"
android:contentDescription="@string/cover_label"
android:layout_width="55dip"
android:layout_height="55dip"
android:layout_alignParentLeft="true"

View File

@ -5,6 +5,7 @@
<ImageView
android:id="@+id/imageView1"
android:contentDescription="@string/external_storage_error_msg"
android:layout_width="30dp"
android:layout_height="30dp"
android:layout_centerHorizontal="true"

View File

@ -26,7 +26,6 @@
<string name="author_label">Müəlif</string>
<string name="language_label">Dil</string>
<string name="podcast_settings_label">Parametrlər</string>
<string name="cover_label">Üz:</string>
<string name="error_label">Xəta</string>
<string name="error_msg_prefix">Xəta baş verdi:</string>
<string name="refresh_label">Təzələ</string>
@ -128,8 +127,7 @@
<string name="action_forbidden_msg">Bu əməliyyat üçün AntennaPod\'un icazəsi yoxdur. AntennaPod\'un keçid tokenin ləğv olunması bunun səbəbi ola bilər. Yenə Flattra girin ya da podkastın səhifəsinə keçin.</string>
<string name="access_revoked_title">Keçid ləğv olundu</string>
<string name="access_revoked_info">AntennaPod\'un keçid tokeni uğurlu ləğv olundu.</string>
<string name="flattr_click_success">Flattrma uğurludur</string>
<string name="flattring_label">Flattrləmə</string>
<!--Flattr-->
<!--Variable Speed-->
<string name="download_plugin_label">Plagin yüklə</string>
<string name="no_playback_plugin_title">Plagin yüklü deyil</string>
@ -235,4 +233,5 @@
<string name="set_to_default_folder">Başlanğıc qovluqu seç</string>
<!--Online feed view-->
<string name="downloading_label">Yükləmə...</string>
<!--Content descriptions for image buttons-->
</resources>

View File

@ -28,7 +28,7 @@
<string name="author_label">Autor</string>
<string name="language_label">Llengua</string>
<string name="podcast_settings_label">Configuració</string>
<string name="cover_label">Caràtula</string>
<string name="cover_label">Imatge</string>
<string name="error_label">Error</string>
<string name="error_msg_prefix">S\'ha produït un error:</string>
<string name="refresh_label">Actualitza</string>
@ -140,8 +140,7 @@
<string name="action_forbidden_msg">AntennaPod no té permisos per executar aquesta acció. És possible que el testimoni d\'accés de Flattr per a AntennaPod hagi estat revocat. Podeu tornar-vos a autenticar amb el servei de Flattr, o podeu visitar el web del contingut directament.</string>
<string name="access_revoked_title">L\'accés ha estat revocat</string>
<string name="access_revoked_info">El testimoni d\'accés a Flattr de l\'AntennaPod s\'ha revocat correctament. Per completar el procés, heu de suprimir aquesta aplicació de la llista d\'aplicacions aprovades que trobareu a l\'apartat de configuració del compte de la plana web de Flattr.</string>
<string name="flattr_click_success">S\'ha compartit el contingut a través de Flattr</string>
<string name="flattring_label">S\'està compartint amb Flattr</string>
<!--Flattr-->
<!--Variable Speed-->
<string name="download_plugin_label">Baixa el connector</string>
<string name="no_playback_plugin_title">Connector no instal·lat</string>
@ -292,4 +291,5 @@
<string name="subscribe_label">Subscriu</string>
<string name="subscribed_label">Subscrit</string>
<string name="downloading_label">S\'està baixant...</string>
<!--Content descriptions for image buttons-->
</resources>

View File

@ -28,7 +28,6 @@
<string name="author_label">Autor</string>
<string name="language_label">Jazyk</string>
<string name="podcast_settings_label">Nastavení</string>
<string name="cover_label">Obal</string>
<string name="error_label">Chyba</string>
<string name="error_msg_prefix">Nastala chyba:</string>
<string name="refresh_label">Obnovit</string>
@ -140,8 +139,7 @@
<string name="action_forbidden_msg">AntennaPod nemá oprávnění pro tuto akci. Důvodem může být revokování přístupového tokenu AntennaPodu k vašemu účtu. Přístup můžete obnovit nebo využít prohlížeče k návštěvě stránky zdroje.</string>
<string name="access_revoked_title">Přístup revokován</string>
<string name="access_revoked_info">Úspěšně revokován přístup AntennPodu k vašemu účtu. Pro dokončení tohoto procesu je ještě zapotřebí na stránkách flattru odebrat z vašeho účtu AntennaPod ze seznamu povolených aplikací.</string>
<string name="flattr_click_success">Úspěšně flattrováno!</string>
<string name="flattring_label">Flattruji</string>
<!--Flattr-->
<!--Variable Speed-->
<string name="download_plugin_label">Stáhnout Plugin</string>
<string name="no_playback_plugin_title">Plugin není nainstalován</string>
@ -292,4 +290,5 @@
<string name="subscribe_label">Odebírat</string>
<string name="subscribed_label">Odebíráno</string>
<string name="downloading_label">Stahuji...</string>
<!--Content descriptions for image buttons-->
</resources>

View File

@ -24,7 +24,6 @@
<string name="cancel_label">Annuller</string>
<string name="author_label">Forfatter</string>
<string name="language_label">Sprog</string>
<string name="cover_label">Cover</string>
<string name="error_label">Fejl</string>
<string name="error_msg_prefix">En fejl er opstået:</string>
<string name="refresh_label">Opdater</string>
@ -126,8 +125,7 @@
<string name="action_forbidden_msg">AntennaPod har ikke tilladelse til denne handling. Årsagen kunne være at adgangspoletten for AntennaPod til din konto er blevet tilbagekaldt. Du kan enten godkende den igen eller besøge websiden for mediet istedet.</string>
<string name="access_revoked_title">Adgang tilbagekaldt</string>
<string name="access_revoked_info">Du har succesfuldt tilbagekaldt AntennaPods adgangs polet til din konto. For at fuldføre processen skal du fjerne denne app fra listen af godkendte applikationer i din kontos indstillinger på flattr\'s hjemmeside.</string>
<string name="flattr_click_success">Det er lykkedes at flattr dette emne!</string>
<string name="flattring_label">Flattere</string>
<!--Flattr-->
<!--Variable Speed-->
<string name="download_plugin_label">Hent Plugin</string>
<string name="no_playback_plugin_title">Plugin er ikke installeret</string>
@ -230,4 +228,5 @@
<string name="folder_not_empty_dialog_msg">Mappen du har valgt er ikke tom. Medie downloads og andre filer vil blive placeret i denne mappe. Forsæt alligevel?</string>
<string name="set_to_default_folder">Vælg standard mappe</string>
<!--Online feed view-->
<!--Content descriptions for image buttons-->
</resources>

View File

@ -28,7 +28,7 @@
<string name="author_label">Autor</string>
<string name="language_label">Sprache</string>
<string name="podcast_settings_label">Einstellungen</string>
<string name="cover_label">Cover</string>
<string name="cover_label">Bild</string>
<string name="error_label">Fehler</string>
<string name="error_msg_prefix">Ein Fehler ist aufgetreten:</string>
<string name="refresh_label">Aktualisieren</string>
@ -140,8 +140,18 @@
<string name="action_forbidden_msg">AntennaPod besitzt keine Erlaubnis für diese Aktion. Der Grund dafür könnte sein, dass AntennaPods Zugangstoken aufgehoben worden ist. Du kannst dich entweder erneut authentifizieren oder die Flattr-Seite der Sache im Web besuchen.</string>
<string name="access_revoked_title">Zugriff widerrufen</string>
<string name="access_revoked_info">Du hast AntennaPod das Zugangstoken zu deinem Account entzogen. Um diesen Prozess abzuschließen, musst du diese Anwendung aus der Liste der zugelassenen Anwendungen in deinen Account Einstellungen auf der Flattr Webseite entfernen.</string>
<string name="flattr_click_success">Du hast erfolgreich diese Sache mit Flattr unterstützt!</string>
<string name="flattring_label">Flattre diese Sache</string>
<!--Flattr-->
<string name="flattr_click_success">Eine Sache wurde geflattrt!</string>
<string name="flattr_click_success_count">%d Sachen wurden geflattrt!</string>
<string name="flattr_click_success_queue">Geflattrt: %s</string>
<string name="flattr_click_failure_count">Flattrn von %d Sachen fehlgeschlagen!</string>
<string name="flattr_click_failure">Nicht geflattrt: %s</string>
<string name="flattr_click_enqueued">Sache wird später gelfattrt</string>
<string name="flattring_thing">Flattrt: %s</string>
<string name="flattring_label">AntennaPod flattrt</string>
<string name="flattrd_label">AntennaPod hat geflattrt</string>
<string name="flattrd_failed_label">AntennaPod Flattrn fehlgeschlagen</string>
<string name="flattr_retrieving_status">Rufe geflatterte Sachen ab</string>
<!--Variable Speed-->
<string name="download_plugin_label">Plugin herunterladen</string>
<string name="no_playback_plugin_title">Plugin nicht installiert</string>
@ -176,6 +186,8 @@
<string name="pref_flattr_this_app_sum">Unterstütze die Entwicklung von AntennaPod mit Flattr. Danke!</string>
<string name="pref_revokeAccess_title">Zugriff entziehen</string>
<string name="pref_revokeAccess_sum">Entziehe dieser Anwendung die Zugriffserlaubnis für deinen Flattr Account.</string>
<string name="pref_auto_flattr_title">Automatisches Flattrn</string>
<string name="pref_auto_flattr_sum">Flattr Episoden, die zu 80 Prozent gespielt wurden.</string>
<string name="pref_display_only_episodes_title">Nur Episoden anzeigen</string>
<string name="pref_display_only_episodes_sum">Zeige nur Feed-Einträge mit Episoden an.</string>
<string name="user_interface_label">Benutzeroberfläche</string>
@ -292,4 +304,21 @@
<string name="subscribe_label">Abonnieren</string>
<string name="subscribed_label">Abonniert</string>
<string name="downloading_label">Lade herunter...</string>
<!--Content descriptions for image buttons-->
<string name="show_chapters_label">Kapitel anzeigen</string>
<string name="show_shownotes_label">Sendungsnotizen anzeigen</string>
<string name="show_cover_label">Bild anzeigen</string>
<string name="rewind_label">Zurückspulen</string>
<string name="fast_forward_label">Vorspulen</string>
<string name="media_type_audio_label">Audio</string>
<string name="media_type_video_label">Video</string>
<string name="navigate_upwards_label">Nach oben navigieren</string>
<string name="butAction_label">Mehr Aktionen</string>
<string name="status_playing_label">Episode wird gerade abgespielt</string>
<string name="status_downloading_label">Episode wird gerade heruntergeladen</string>
<string name="status_downloaded_label">Episode ist heruntergeladen</string>
<string name="status_unread_label">Eintrag ist neu</string>
<string name="in_queue_label">Episode befindet sich inder Abspielliste</string>
<string name="new_episodes_count_label">Anzahl neuer Episoden</string>
<string name="in_progress_episodes_count_label">Anzahl der Episoden, die du angefangen hast zu hören</string>
</resources>

View File

@ -24,7 +24,6 @@
<string name="cancel_label">Cancelar</string>
<string name="author_label">Autor</string>
<string name="language_label">Idioma</string>
<string name="cover_label">Carátula</string>
<string name="error_label">Error</string>
<string name="error_msg_prefix">Ha ocurrido un error:</string>
<string name="refresh_label">Actualizar</string>
@ -122,8 +121,7 @@
<string name="action_forbidden_msg">AntennaPod no tiene permiso para realizar esta acción. La razón puede ser que se haya revocado el token de acceso de AntennaPod para su cuenta. Puede re-autenticarse o visitar la página web de la cosa.</string>
<string name="access_revoked_title">Acceso revocado</string>
<string name="access_revoked_info">Ha revocado el token de acceso de AntennaPod a su cuenta. Para completar el proceso debe eliminar esta aplicación de la lista de aplicaciones aprobadas, en los ajustes de Flattr.</string>
<string name="flattr_click_success">Ha valorado esto en Flattr.</string>
<string name="flattring_label">Valoración en Flattr</string>
<!--Flattr-->
<!--Variable Speed-->
<!--Empty list labels-->
<string name="no_items_label">Esta lista no tiene elementos.</string>
@ -218,4 +216,5 @@
<string name="folder_not_empty_dialog_msg">La carpeta elegida no está vacía. Las descargas y otros archivos se copiarán directamente en esta carpeta. ¿Continuar igualmente?</string>
<string name="set_to_default_folder">Elegir carpeta predeterminada</string>
<!--Online feed view-->
<!--Content descriptions for image buttons-->
</resources>

View File

@ -28,7 +28,7 @@
<string name="author_label">Autor</string>
<string name="language_label">Idioma</string>
<string name="podcast_settings_label">Ajustes</string>
<string name="cover_label">Carátula</string>
<string name="cover_label">Imagen</string>
<string name="error_label">Error</string>
<string name="error_msg_prefix">Ha ocurrido un error:</string>
<string name="refresh_label">Actualizar</string>
@ -140,8 +140,18 @@
<string name="action_forbidden_msg">AntennaPod no tiene permiso para realizar esta acción. La razón puede ser que se haya revocado el token de acceso de AntennaPod para su cuenta. Puede re-autenticarse o visitar la página web de la cosa.</string>
<string name="access_revoked_title">Acceso revocado</string>
<string name="access_revoked_info">Ha revocado el token de acceso de AntennaPod a su cuenta. Para completar el proceso debe eliminar esta aplicación de la lista de aplicaciones aprobadas, en los ajustes de Flattr.</string>
<string name="flattr_click_success">Ha valorado esto en Flattr.</string>
<string name="flattring_label">Valoración en Flattr</string>
<!--Flattr-->
<string name="flattr_click_success">¡Flattr una cosa!</string>
<string name="flattr_click_success_count">¡Flattr %d cosas!</string>
<string name="flattr_click_success_queue">Flattr: %s.</string>
<string name="flattr_click_failure_count">¡Falló Flattr de %d cosas!</string>
<string name="flattr_click_failure">No se hizo Flattr: %s.</string>
<string name="flattr_click_enqueued">Se hará Flattr de esta cosa más tarde</string>
<string name="flattring_thing">Haciendo Flattr de %s</string>
<string name="flattring_label">AntennaPod haciendo Flattr</string>
<string name="flattrd_label">AntennaPod hizo Flattr</string>
<string name="flattrd_failed_label">AntennaPod Flattr falló</string>
<string name="flattr_retrieving_status">Obteniendo lista de Flattr</string>
<!--Variable Speed-->
<string name="download_plugin_label">Descargar complemento</string>
<string name="no_playback_plugin_title">Complemento no instalado</string>
@ -176,6 +186,8 @@
<string name="pref_flattr_this_app_sum">Apoye el desarrollo de AntennaPod valorándola en Flattr. ¡Gracias!</string>
<string name="pref_revokeAccess_title">Revocar el acceso</string>
<string name="pref_revokeAccess_sum">Rescindir el permiso de acceso de esta aplicación a su cuenta de Flattr.</string>
<string name="pref_auto_flattr_title">Uso de Flattr automático</string>
<string name="pref_auto_flattr_sum">Hacer Flattr al reproducir el 80 por ciento de cada episodio</string>
<string name="pref_display_only_episodes_title">Mostrar solo episodios</string>
<string name="pref_display_only_episodes_sum">Mostrar solo elementos que contengan un episodio.</string>
<string name="user_interface_label">Interfaz de usuario</string>
@ -292,4 +304,21 @@
<string name="subscribe_label">Suscribirse</string>
<string name="subscribed_label">Suscrito</string>
<string name="downloading_label">Descargando…</string>
<!--Content descriptions for image buttons-->
<string name="show_chapters_label">Mostrar capítulos</string>
<string name="show_shownotes_label">Mostrar notas del programa</string>
<string name="show_cover_label">Mostrar imagen</string>
<string name="rewind_label">Rebobinar</string>
<string name="fast_forward_label">Avance rápido</string>
<string name="media_type_audio_label">Audio</string>
<string name="media_type_video_label">Vídeo</string>
<string name="navigate_upwards_label">Navegar hacia arriba</string>
<string name="butAction_label">Más acciones</string>
<string name="status_playing_label">El episodio se está reproduciendo</string>
<string name="status_downloading_label">El episodio se está descargando</string>
<string name="status_downloaded_label">El episodio está descargado</string>
<string name="status_unread_label">El elemento es nuevo</string>
<string name="in_queue_label">El episodio está en la cola</string>
<string name="new_episodes_count_label">Cantidad de episodios nuevos</string>
<string name="in_progress_episodes_count_label">Cantidad de episodios que ha comenzado a escuchar</string>
</resources>

View File

@ -28,7 +28,7 @@
<string name="author_label">Auteur</string>
<string name="language_label">Langue</string>
<string name="podcast_settings_label">Préférences</string>
<string name="cover_label">Couverture</string>
<string name="cover_label">Image</string>
<string name="error_label">Erreur</string>
<string name="error_msg_prefix">Une erreur a eu lieu :</string>
<string name="refresh_label">Rafraîchir</string>
@ -140,8 +140,18 @@
<string name="action_forbidden_msg">AntennaPod n\'a pas la permission pour cette action. Il est possible que l\'accès à votre compte depuis AntennaPod ait été révoqué. Vous pouvez vous authentifier à nouveau, ou bien visiter le site à flattr directement.</string>
<string name="access_revoked_title">Accès révoqué</string>
<string name="access_revoked_info">Vous avez révoqué le jeton d\'accès d\'AntennaPod à votre compte. Pour terminer cette opération, vous devez retirer AntennaPod de la liste des applications autorisées sur le site web de Flattr.</string>
<string name="flattr_click_success">Flattr réussi !</string>
<string name="flattring_label">Flattr en cours</string>
<!--Flattr-->
<string name="flattr_click_success">Une chose de Flattré !</string>
<string name="flattr_click_success_count">%d choses de Flattré !</string>
<string name="flattr_click_success_queue">Flattré : %s.</string>
<string name="flattr_click_failure_count">Impossible de Flattrer %d choses !</string>
<string name="flattr_click_failure">Non Flattré : %s.</string>
<string name="flattr_click_enqueued">Cette chose sera Flattré plus tard</string>
<string name="flattring_thing">En train de Flattrer %s</string>
<string name="flattring_label">AntennaPod est en train de Flattrer</string>
<string name="flattrd_label">AntennaPod a Flattré</string>
<string name="flattrd_failed_label">Flattr d\'AntennaPod a échoué</string>
<string name="flattr_retrieving_status">Obtention de la liste des choses Flattrées</string>
<!--Variable Speed-->
<string name="download_plugin_label">Télécharger une extension</string>
<string name="no_playback_plugin_title">Extension non installée</string>
@ -176,6 +186,8 @@
<string name="pref_flattr_this_app_sum">Encouragez le développement d\'AntennaPod grâce à Flattr. Merci !</string>
<string name="pref_revokeAccess_title">Révoquer l\'accès</string>
<string name="pref_revokeAccess_sum">Révoquer la permission d\'accès à votre compte Flattr depuis cette application.</string>
<string name="pref_auto_flattr_title">Flattr automatique</string>
<string name="pref_auto_flattr_sum">Flattrer les épisodes dont 80 pour-cents ont été joués.</string>
<string name="pref_display_only_episodes_title">N\'afficher que les épisodes</string>
<string name="pref_display_only_episodes_sum">N\'afficher que les flux qui ont au moins un épisode.</string>
<string name="user_interface_label">Interface utilisateur</string>
@ -292,4 +304,21 @@
<string name="subscribe_label">S\'abonner</string>
<string name="subscribed_label">Abonné</string>
<string name="downloading_label">Téléchargement en cours</string>
<!--Content descriptions for image buttons-->
<string name="show_chapters_label">Afficher chapitres</string>
<string name="show_shownotes_label">Afficher notes d\'épisode</string>
<string name="show_cover_label">Afficher image</string>
<string name="rewind_label">Retour en arrière</string>
<string name="fast_forward_label">Avance rapide</string>
<string name="media_type_audio_label">Audio</string>
<string name="media_type_video_label">Vidéo</string>
<string name="navigate_upwards_label">Naviguer vers le haut</string>
<string name="butAction_label">Plus d\'actions</string>
<string name="status_playing_label">L\'épisode est en train d\'être joué</string>
<string name="status_downloading_label">L\'épisode est en train d\'être téléchargé</string>
<string name="status_downloaded_label">L\'épisode a été téléchargé</string>
<string name="status_unread_label">L\'élément est nouveau</string>
<string name="in_queue_label">L\'épisode est dans la liste</string>
<string name="new_episodes_count_label">Nombre de nouveaux épisodes</string>
<string name="in_progress_episodes_count_label">Nombre d\'épisodes que vous avez commencé à écouter</string>
</resources>

View File

@ -28,7 +28,6 @@
<string name="author_label">\tनिर्माता</string>
<string name="language_label">भाषा</string>
<string name="podcast_settings_label">सेटिंग्स</string>
<string name="cover_label">आवरण</string>
<string name="error_label">त्रुटि</string>
<string name="error_msg_prefix">एक त्रुटि हो गई:</string>
<string name="refresh_label">ताज़ा करें</string>
@ -140,8 +139,7 @@
<string name="action_forbidden_msg">AntennaPod को इस कार्रवाई के लिए अनुमति नहीं है.इस के लिए कारण हो सकता है की आपके खाते में AntennaPod की पहुँच टोकन को निरस्त किया गया है.आप या तो फिर से प्रमाणित कर सकते हैं या बजाय किसी बात के वेबसाइट पर जा सकते हैं.</string>
<string name="access_revoked_title">प्रवेश निरस्त किया</string>
<string name="access_revoked_info">आपने सफलतापूर्वक अपने खाते में AntennaPod पहुँच टोकन निरस्त कर दिया है. इस प्रक्रिया को पूरा करने के लिए, आपको flattr वेबसाइट पर अपने खाते की सेटिंग्स में अनुमोदित आवेदनों की सूची से इस एप्लिकेशन को हटाना होगा.</string>
<string name="flattr_click_success">सफलतापूर्वक इस बात flattred कर दिया गया है!</string>
<string name="flattring_label">Flattr करें</string>
<!--Flattr-->
<!--Variable Speed-->
<string name="download_plugin_label">प्लगइन डाउनलोड करें</string>
<string name="no_playback_plugin_title">प्लगइन स्थापित नहीं हुआ</string>
@ -292,4 +290,5 @@
<string name="subscribe_label">सदस्यता लें</string>
<string name="subscribed_label">सदस्यता ली गई</string>
<string name="downloading_label">डाउनलोड कर रहा है ...</string>
<!--Content descriptions for image buttons-->
</resources>

View File

@ -28,7 +28,7 @@
<string name="author_label">Autore</string>
<string name="language_label">Lingua</string>
<string name="podcast_settings_label">Impostazioni</string>
<string name="cover_label">Copertina</string>
<string name="cover_label">Immagine</string>
<string name="error_label">Errore</string>
<string name="error_msg_prefix">Un errore è stato rilevato:</string>
<string name="refresh_label">Aggiorna</string>
@ -140,8 +140,8 @@
<string name="action_forbidden_msg">AntennaPod non ha il permesso di effettuare questa azione. La ragione potrebbe essere che il token di accesso di AntennaPod al tuo account è stato revocato. Puoi eseguire la re-autenticazione o altrimenti visitare il sito web.</string>
<string name="access_revoked_title">Accesso revocato</string>
<string name="access_revoked_info">Hai revocato l\'accesso di AntennaPod al tuo account. Al fine di completare il processo devi rimuovere l\'app dalla lista delle applicazioni autorizzare nelle impostazioni del tuo account sul sito di flattr.</string>
<string name="flattr_click_success">Flattr eseguito con successo!</string>
<string name="flattring_label">Flattr in corso</string>
<!--Flattr-->
<string name="flattring_label">AntennaPod sta eseguendo Flattr</string>
<!--Variable Speed-->
<string name="download_plugin_label">Scarica Plugin</string>
<string name="no_playback_plugin_title">Plugin non installato</string>
@ -292,4 +292,20 @@
<string name="subscribe_label">Abbonati</string>
<string name="subscribed_label">Abbonato</string>
<string name="downloading_label">Download in corso...</string>
<!--Content descriptions for image buttons-->
<string name="show_chapters_label">Mostra i capitoli</string>
<string name="show_shownotes_label">Mostra le note dell\'episodio</string>
<string name="show_cover_label">Mosta l\'immagine</string>
<string name="rewind_label">Riavvolgi</string>
<string name="fast_forward_label">Avanti veloce</string>
<string name="media_type_audio_label">Audio</string>
<string name="media_type_video_label">Video</string>
<string name="navigate_upwards_label">Naviga su</string>
<string name="butAction_label">Più azioni</string>
<string name="status_playing_label">L\'episodio è in corso di ripoduzione</string>
<string name="status_downloading_label">L\'episodio sta per essere scaricato</string>
<string name="status_downloaded_label">L\'episodio è stato scaricato</string>
<string name="status_unread_label">L\'oggetto è nuovo</string>
<string name="in_queue_label">L\'episodio è in coda</string>
<string name="new_episodes_count_label">Numero dei nuovi episodi</string>
</resources>

View File

@ -8,10 +8,13 @@
<string name="new_label">Nieuw</string>
<string name="waiting_list_label">Wachtlijst</string>
<string name="settings_label">Instellingen</string>
<string name="add_new_feed_label">Podcast toevoegen</string>
<string name="downloads_label">Downloads</string>
<string name="cancel_download_label">Annuleer download</string>
<string name="download_log_label">Download log</string>
<string name="playback_history_label">Afspeelgeschiedenis</string>
<string name="gpodnet_main_label">gpodder.net</string>
<string name="gpodnet_auth_label">gpodder.net login</string>
<!--Webview actions-->
<string name="open_in_browser_label">In de browser openen</string>
<string name="copy_url_label">URL kopieren</string>
@ -24,13 +27,15 @@
<string name="cancel_label">Annuleer</string>
<string name="author_label">Auteur</string>
<string name="language_label">Taal</string>
<string name="cover_label">Cover</string>
<string name="podcast_settings_label">Instellingen</string>
<string name="cover_label">Beeld</string>
<string name="error_label">Fout</string>
<string name="error_msg_prefix">Er is een fout opgetreden:</string>
<string name="refresh_label">Verversen</string>
<string name="external_storage_error_msg">Geen externe opslag beschikbaar. Zorg ervoor dat de externe opslag gemonteerd is, zodat de app goed kan werken.</string>
<string name="chapters_label">Hoofdstukken</string>
<string name="shownotes_label">Shownotes</string>
<string name="description_label">Beschrijving</string>
<string name="most_recent_prefix">Meest recent episode:\u0020</string>
<string name="episodes_suffix">\u0020episodes</string>
<string name="published_prefix">Gepubliceerd:\u0020</string>
@ -39,9 +44,14 @@
<string name="processing_label">Aan het verwerken</string>
<string name="loading_label">Laden...</string>
<string name="image_of_prefix">Beeld van:\u0020</string>
<string name="save_username_password_label">Gebruikersnaam en wachtwoord opslaan</string>
<string name="close_label">Sluiten</string>
<string name="retry_label">Opnieuw proberen</string>
<string name="auto_download_label">Voor het automatisch downloaden beschouwen</string>
<!--'Add Feed' Activity labels-->
<string name="feedurl_label">Feed URL</string>
<string name="txtvfeedurl_label">Podcast toevoegen bij URL</string>
<string name="podcastdirectories_label">Podcast lijsten</string>
<!--Actions on feeds-->
<string name="mark_all_read_label">Alles als gelezen markeren</string>
<string name="show_info_label">Toon informatie</string>
@ -49,6 +59,7 @@
<string name="share_link_label">Website link delen</string>
<string name="share_source_label">Feed link delen</string>
<string name="feed_delete_confirmation_msg">Bevestig dat u deze feed en ALLE episodes van deze feed die u hebt gedownload wilt verwijderen.</string>
<string name="feed_remover_msg">Feed verwijderen</string>
<!--actions on feeditems-->
<string name="download_label">Download</string>
<string name="play_label">Spelen</string>
@ -84,6 +95,7 @@
<string name="download_error_malformed_url">Misvormde URL</string>
<string name="download_error_io_error">IO fout</string>
<string name="download_error_request_error">Fout in de aanvraag</string>
<string name="download_error_db_access">Databasetoegangsfout</string>
<string name="downloads_left">Nog \u0020 downloads</string>
<string name="download_notification_title">Podcast gegevens aan het downloaden</string>
<string name="download_report_content">%1$d downloads geslaagd, %2$d mislukt</string>
@ -113,6 +125,8 @@
<string name="organize_queue_label">Wachtrij organiseren</string>
<string name="undo">Ongedaan maken</string>
<string name="removed_from_queue">Item verwijderd</string>
<string name="move_to_top_label">Naar boven verplaatsen</string>
<string name="move_to_bottom_label">Naar beneden verplaatsen</string>
<!--Flattr-->
<string name="flattr_auth_label">Flattr inloggen</string>
<string name="flattr_auth_explanation">Druk op onderstaande knop om het verificatieproces te starten. U wordt doorgestuurd naar de Flattr inlogscherm in uw browser en wordt gevraagd om toestemming aan AntennaPod te geven om dingen te Flattr\'en. Nadat u toestemming hebt gegeven, keert u automatisch terug naar dit scherm.</string>
@ -126,8 +140,7 @@
<string name="action_forbidden_msg">AntennaPod heeft geen toestemming voor deze actie. De reden hiervoor zou kunnen zijn dat de toegang token van AntennaPod voor uw account ingetrokken is. U kunt opnieuw authenticeren, of de website van het ding bezoeken.</string>
<string name="access_revoked_title">Toegang ingetrokken</string>
<string name="access_revoked_info">U heeft met succes het toegangstoken van AntennaPod tot uw account ingetrokken. Om het proces te voltooien, moet u deze app uit de lijst van goedgekeurde applicaties in uw accountinstellingen op de Flattr website verwijderen.</string>
<string name="flattr_click_success">Dit ding met succes geflattr\'d!</string>
<string name="flattring_label">Aan het flattr\'en</string>
<!--Flattr-->
<!--Variable Speed-->
<string name="download_plugin_label">Plugin downloaden</string>
<string name="no_playback_plugin_title">Plugin niet geinstalleerd</string>
@ -140,6 +153,8 @@
<string name="other_pref">Overig</string>
<string name="about_pref">Over AntennaPod</string>
<string name="queue_label">Wachtrij</string>
<string name="services_label">Services</string>
<string name="flattr_label">Flattr</string>
<string name="pref_pauseOnHeadsetDisconnect_sum">Afspelen pauzeren wanneer de hoofdtelefoon wordt losgekoppeld</string>
<string name="pref_followQueue_sum">Volgende wachtrij item afspelen als de episode voltooid is</string>
<string name="playback_pref">Afspelen</string>
@ -168,7 +183,7 @@
<string name="pref_automatic_download_title">Automatisch downloaden</string>
<string name="pref_automatic_download_sum">Configureer het automatisch downloaden van episoden.</string>
<string name="pref_autodl_wifi_filter_title">Wi-Fi filter inschakelen</string>
<string name="pref_autodl_wifi_filter_sum">Automatisch download alleen voor geselecteerde Wi-Fi-netwerken toestaan.</string>
<string name="pref_autodl_wifi_filter_sum">Automatisch downloaden alleen toestaan voor geselecteerde Wi-Fi-netwerken.</string>
<string name="pref_episode_cache_title">Episode cache</string>
<string name="pref_theme_title_light">Licht</string>
<string name="pref_theme_title_dark">Donker</string>
@ -176,8 +191,16 @@
<string name="pref_update_interval_hours_plural">uren</string>
<string name="pref_update_interval_hours_singular">uur</string>
<string name="pref_update_interval_hours_manual">Handmatig</string>
<string name="pref_gpodnet_authenticate_title">Log in</string>
<string name="pref_gpodnet_authenticate_sum">Log met je gpodder.net account in om je abonnementen te synchroniseren.</string>
<string name="pref_gpodnet_logout_title">Log uit</string>
<string name="pref_gpodnet_logout_toast">Uitlog was succesvol</string>
<string name="pref_gpodnet_setlogin_information_title">Aanmeldingsgegevens wijzigen</string>
<string name="pref_gpodnet_setlogin_information_sum">Wijzig de aanmeldingsgegevens van je gpodder.net account.</string>
<string name="pref_playback_speed_title">Afspeelsnelheden</string>
<string name="pref_playback_speed_sum">Pas de beschikbare snelheden aan voor de variabele audio afspeelsnelheid</string>
<string name="pref_gpodnet_sethostname_title">Definieer hostname</string>
<string name="pref_gpodnet_sethostname_use_default_host">Gebruik standaard host</string>
<!--Search-->
<string name="search_hint">Feeds of episodes zoeken</string>
<string name="found_in_shownotes_label">Gevonden in de shownotes</string>
@ -189,6 +212,7 @@
<string name="search_label">Zoeken</string>
<string name="found_in_title_label">Gevonden in de titel</string>
<!--OPML import and export-->
<string name="opml_import_txtv_button_lable">Met OPML-bestanden kan je podcasts van de ene naar de andere podcatcher verplaatsen.</string>
<string name="opml_import_explanation">Om een OPML-bestand te importeren moet je het in de volgende map zetten en op onderstaande knop drukken.</string>
<string name="start_import_label">Start importeren</string>
<string name="opml_import_label">OPML import</string>
@ -219,8 +243,36 @@
<string name="miro_search_hint">Zoek Miro gids</string>
<string name="popular_label">Populair</string>
<string name="best_rating_label">Beste beoordeling</string>
<string name="add_feed_label">Podcast toevoegen</string>
<string name="miro_feed_added">Feed wordt toegevoegd</string>
<!--gpodder.net-->
<string name="gpodnet_taglist_header">CATEGORIEËN</string>
<string name="gpodnet_toplist_header">TOP PODCASTS</string>
<string name="gpodnet_suggestions_header">SUGGESTIES</string>
<string name="gpodnet_search_hint">Zoek gpodder.net</string>
<string name="gpodnetauth_login_title">Log in</string>
<string name="gpodnetauth_login_descr">Welkom op de gpodder.net login proces. Eerst, typ je login gegevens:</string>
<string name="gpodnetauth_login_butLabel">Log in</string>
<string name="gpodnetauth_login_register">Als je nog geen account hebt, kun je er hier een aanmaken:\n https://gpodder.net/register/</string>
<string name="username_label">Gebruikersnaam</string>
<string name="password_label">Wachtwoord</string>
<string name="gpodnetauth_device_title">Apparaatselectie</string>
<string name="gpodnetauth_device_descr">Maak een nieuw apparaat aan om voor je gpodder.net account te gebruiken of kies een bestaande:</string>
<string name="gpodnetauth_device_deviceID">Device ID:\u0020</string>
<string name="gpodnetauth_device_caption">Titel</string>
<string name="gpodnetauth_device_butCreateNewDevice">Maak een nieuw apparaat aan</string>
<string name="gpodnetauth_device_chooseExistingDevice">Kies een bestaand apparaat:</string>
<string name="gpodnetauth_device_errorEmpty">Apparaat ID mag niet leeg zijn</string>
<string name="gpodnetauth_device_errorAlreadyUsed">Apparaat ID al in gebruik</string>
<string name="gpodnetauth_device_butChoose">Kies</string>
<string name="gpodnetauth_finish_title">Login succesvol</string>
<string name="gpodnetauth_finish_descr">Gefeliciteerd! Jou gpodder.net account is nu verbonden met je apparaat. AntennaPod zal voortaan abonnementen op je apparaat automatisch met je gpodder.net account synchroniseren.</string>
<string name="gpodnetauth_finish_butsyncnow">Synchronisatie nu starten</string>
<string name="gpodnetauth_finish_butgomainscreen">Terug naar hoofdscherm</string>
<string name="gpodnetsync_auth_error_title">gpodder.net authenticatie fout</string>
<string name="gpodnetsync_auth_error_descr">Ongeldig gebruikersnaam of wachtwoord</string>
<string name="gpodnetsync_error_title">gpodder.net synchronisatie fout</string>
<string name="gpodnetsync_error_descr">Er is een fout opgetreden tijdens het synchroniseren:\u0020</string>
<!--Directory chooser-->
<string name="selected_folder_label">Geselecteerde map:</string>
<string name="create_folder_label">Map aanmaken</string>
@ -233,5 +285,14 @@
<string name="folder_not_empty_dialog_title">Map is niet leeg</string>
<string name="folder_not_empty_dialog_msg">De map die je hebt gekozen is niet leeg. Media downloads en andere bestanden zullen rechtstreeks in deze map geplaatst worden. Toch doorgaan?</string>
<string name="set_to_default_folder">Kies default map</string>
<string name="pref_pausePlaybackForFocusLoss_sum">Het afspelen onderbreken in plaats van het volume te verlagen wanneer er een andere app geluiden af wilt spelen</string>
<string name="pref_pausePlaybackForFocusLoss_title">Pauze voor onderbrekingen</string>
<!--Online feed view-->
<string name="subscribe_label">Abonneren</string>
<string name="subscribed_label">Geabonneerd</string>
<string name="downloading_label">Aan het downloaden</string>
<!--Content descriptions for image buttons-->
<string name="media_type_audio_label">Audio</string>
<string name="media_type_video_label">Video</string>
<string name="butAction_label">Meer acties</string>
</resources>

View File

@ -0,0 +1,307 @@
<?xml version='1.0' encoding='UTF-8'?>
<resources xmlns:tools="http://schemas.android.com/tools">
<!--Activitiy titles-->
<string name="app_name">AntennaPod</string>
<string name="feeds_label">Kanały</string>
<string name="podcasts_label">PODCASTY</string>
<string name="episodes_label">ODCINKI</string>
<string name="new_label">Nowy</string>
<string name="waiting_list_label">Lista oczekujących</string>
<string name="settings_label">Ustawienia</string>
<string name="add_new_feed_label">Dodaj podcast</string>
<string name="downloads_label">Pobrane</string>
<string name="cancel_download_label">Anuluj pobieranie</string>
<string name="download_log_label">Dziennik pobierania</string>
<string name="playback_history_label">Historia odtwarzania</string>
<string name="gpodnet_main_label">gpodder.net</string>
<string name="gpodnet_auth_label">gpodder.net login</string>
<!--Webview actions-->
<string name="open_in_browser_label">Otwórz w przeglądarce</string>
<string name="copy_url_label">Kopiuj adres</string>
<string name="share_url_label">Udostępnij adres</string>
<string name="copied_url_msg">Skopiowano adres do schowka.</string>
<!--Playback history-->
<string name="clear_history_label">Wyczyść historię</string>
<!--Other-->
<string name="confirm_label">Potwierdź </string>
<string name="cancel_label">Anuluj</string>
<string name="author_label">Autor</string>
<string name="language_label">Język</string>
<string name="podcast_settings_label">Ustawienia</string>
<string name="error_label">Błąd</string>
<string name="error_msg_prefix">Wystąpił błąd:</string>
<string name="refresh_label">Odśwież</string>
<string name="external_storage_error_msg">Brak zewnętrznej pamięci. Sprawdź czy jest ona podłączona żeby aplikacja mogła pracować poprawnie.</string>
<string name="chapters_label">Rozdziały</string>
<string name="shownotes_label">Pokaż notatki</string>
<string name="description_label">Opis</string>
<string name="most_recent_prefix">Najnowszy odcinek:\u0020</string>
<string name="episodes_suffix">:\u0020odcinków</string>
<string name="published_prefix">Opublikowane:\u0020</string>
<string name="length_prefix">Długość:\u0020</string>
<string name="size_prefix">Rozmiar:\u0020</string>
<string name="processing_label">Przetwarzanie</string>
<string name="loading_label">Ładowanie...</string>
<string name="image_of_prefix">Obraz z:\u0020</string>
<string name="save_username_password_label">Zapisz nazwę użytkownika i hasło</string>
<string name="close_label">Zamknij</string>
<string name="retry_label">Spróbuj ponownie</string>
<string name="auto_download_label">Dołącz do automatycznego pobierania</string>
<!--'Add Feed' Activity labels-->
<string name="feedurl_label">Adres kanału</string>
<string name="txtvfeedurl_label">Dodaj podcast przez adres</string>
<string name="podcastdirectories_label">Katalogi podcastów</string>
<!--Actions on feeds-->
<string name="mark_all_read_label">Oznacz wszystkie jako przeczytane</string>
<string name="show_info_label">Pokaż informacje</string>
<string name="remove_feed_label">Usuń kanał</string>
<string name="share_link_label">Udostępnij stronę</string>
<string name="share_source_label">Udostępnij kanał</string>
<string name="feed_delete_confirmation_msg">Potwierdź chęć usunięcia tego kanału wraz ze WSZYSTKIMI odcinkami, które zostały pobrane.</string>
<string name="feed_remover_msg">Usuwanie kanału</string>
<!--actions on feeditems-->
<string name="download_label">Pobierz</string>
<string name="play_label">Odtwórz</string>
<string name="pause_label">Pauza</string>
<string name="stream_label">Strumień</string>
<string name="remove_label">Usuń</string>
<string name="mark_read_label">Oznacz jako przeczytane</string>
<string name="mark_unread_label">Oznacz jako nieprzeczytane</string>
<string name="add_to_queue_label">Dodaj do kolejki</string>
<string name="remove_from_queue_label">Usuń z kolejki</string>
<string name="visit_website_label">Odwiedź stronę</string>
<string name="support_label">Wspomóż na Flattr</string>
<string name="enqueue_all_new">Dodaj wszystko do kolejki</string>
<string name="download_all">Pobierz wszystkie</string>
<string name="skip_episode_label">Pomiń odcinek</string>
<!--Download messages and labels-->
<string name="download_successful">Pobieranie ukończone</string>
<string name="download_failed">Pobieranie nieudane</string>
<string name="download_pending">Pobieranie w toku</string>
<string name="download_running">Pobieram</string>
<string name="download_error_device_not_found">Nie znaleziono urządzenia docelowego</string>
<string name="download_error_insufficient_space">Nie wystarczająca ilość pamięci</string>
<string name="download_error_file_error">Błąd pliku</string>
<string name="download_error_http_data_error">Błąd danych HTTP</string>
<string name="download_error_error_unknown">Nieznany błąd</string>
<string name="download_error_parser_exception">Wyjątek parsera</string>
<string name="download_error_unsupported_type">Nieobsługiwany typ kanału</string>
<string name="download_error_connection_error">Błąd połączenia</string>
<string name="download_error_unknown_host">Nieznany host</string>
<string name="cancel_all_downloads_label">Anuluj wszystkie pobierania</string>
<string name="download_cancelled_msg">Pobieranie anulowane</string>
<string name="download_report_title">Pobieranie ukończone</string>
<string name="download_error_malformed_url">Niepoprawny adres</string>
<string name="download_error_io_error">Błąd wejścia/wyjścia</string>
<string name="download_error_request_error">Błąd żądania</string>
<string name="download_error_db_access">Błąd dostępu do bazy danych</string>
<string name="downloads_left">:\u0020pobrań pozostało</string>
<string name="download_notification_title">Pobieranie danych podcastu</string>
<string name="download_report_content">%1$d pobierania poprawne, %2$d nieudane</string>
<string name="download_log_title_unknown">Nieznany tytuł</string>
<string name="download_type_feed">Kanał</string>
<string name="download_type_media">Plik multimedialny</string>
<string name="download_type_image">Obraz</string>
<string name="download_request_error_dialog_message_prefix">Wystąpił błąd przy próbie pobierania:\u0020</string>
<!--Mediaplayer messages-->
<string name="player_error_msg">Błąd!</string>
<string name="player_stopped_msg">Żadne media nie odtwarzane </string>
<string name="player_preparing_msg">Przygotowuję</string>
<string name="player_ready_msg">Gotowe</string>
<string name="player_seeking_msg">Szukam</string>
<string name="playback_error_server_died">Serwer zdechł</string>
<string name="playback_error_unknown">Nieznany błąd</string>
<string name="no_media_playing_label">Żadne media nie odtwarzane </string>
<string name="position_default_label">00:00:00</string>
<string name="player_buffering_msg">Buferowanie</string>
<string name="playbackservice_notification_title">Odtwarzenie podcastu </string>
<string name="playbackservice_notification_content">Więcej informacji</string>
<!--Navigation-->
<string name="show_download_log">Pokaż log</string>
<string name="show_player_label">Pokaż odtwarzacz</string>
<!--Queue operations-->
<string name="clear_queue_label">Wyczyść kolejkę</string>
<string name="organize_queue_label">Organizuj kolejkę</string>
<string name="undo">Cofnij</string>
<string name="removed_from_queue">Element usunięty</string>
<string name="move_to_top_label">Przesuń na górę</string>
<string name="move_to_bottom_label">Przesuń na dół</string>
<!--Flattr-->
<string name="flattr_auth_label">Logowanie do Flattr</string>
<string name="flattr_auth_explanation">Naciśnij przycisk poniżej by zacząć proces autoryzacji. Zostaniesz przekierowany na stronę logowanie do flattr w przeglądarce i zostaniesz poproszony o przyznanie zezwolenia AntennaPod-owi na flattr-owanie. Po daniu zezwolenia powrócisz do tej strony automatycznie.</string>
<string name="authenticate_label">Autoryzacja</string>
<string name="return_home_label">Wróć do ekranu głównego</string>
<string name="flattr_auth_success">Autoryzacja się powiodła. Możesz teraz używać flattr w aplikacji.</string>
<string name="no_flattr_token_title">Nie znaleziono tokenu Flattr</string>
<string name="no_flattr_token_msg">Twoje konto Flattr wydaje się nie być podłączone do AntennaPod. Możesz połączyć konto do AntennaPod by przez program flattr-ować lub możesz odwiedzić stronę wątku by zrobić to tam.</string>
<string name="authenticate_now_label">Autoryzuj</string>
<string name="action_forbidden_title">Akcja zabroniona</string>
<string name="action_forbidden_msg">AntennaPod nie ma zezwolenia na tą akcję. Powodem może być fakt iż dostęp dla AntennaPod do Twojego konta został cofnięty. Możesz ponownie autoryzować aplikację lub odwiedzić stronę. </string>
<string name="access_revoked_title">Anulowano dostęp</string>
<string name="access_revoked_info">Odwołałeś dostęp AntennaPod do swojego konta. W celu zakończenia procesu musisz usunąć aplikację z listy aplikacji dozwolonych na koncie Flattr.</string>
<!--Flattr-->
<!--Variable Speed-->
<string name="download_plugin_label">Pobierz wtyczkę</string>
<string name="no_playback_plugin_title">Wtyczka nie zainstalowana</string>
<string name="no_playback_plugin_msg">Do odtwarzania ze zmienną prędkością jest potrzebna biblioteka innej firmy. \n\nDotknij przycisku \"Pobierz wtyczkę\", aby pobrać darmową wtyczkę ze sklepu\n\nWszelkie znalezione za pomocą tej wtyczki problemy nie są odpowiedzialnością AntennaPod i należy zgłosić się do właściciela plugin.</string>
<string name="set_playback_speed_label">Prędkość odtwarzania</string>
<!--Empty list labels-->
<string name="no_items_label">Brak elementów na tej liście.</string>
<string name="no_feeds_label">Nie subskrybowałeś jeszcze żadnego kanału.</string>
<!--Preferences-->
<string name="other_pref">Inne</string>
<string name="about_pref">O...</string>
<string name="queue_label">Kolejka</string>
<string name="services_label">Usługi</string>
<string name="flattr_label">Flattr</string>
<string name="pref_pauseOnHeadsetDisconnect_sum">Wstrzymaj odtwarzanie kiedy słuchawki zostaną odłączone</string>
<string name="pref_followQueue_sum">Przeskocz do następnego elementu kolejki po zakończeniu odtwarzania</string>
<string name="playback_pref">Odtwarzanie</string>
<string name="network_pref">Sieć</string>
<string name="pref_autoUpdateIntervall_title">Częstość aktualizacji</string>
<string name="pref_autoUpdateIntervall_sum">Określ częstotliwość automatycznego odświeżania lub je wyłącz</string>
<string name="pref_downloadMediaOnWifiOnly_sum">Pobieraj pliki tylko przez WiFi</string>
<string name="pref_followQueue_title">Odtwarzanie ciągłe</string>
<string name="pref_downloadMediaOnWifiOnly_title">WiFi media pobrane</string>
<string name="pref_pauseOnHeadsetDisconnect_title">Słuchawki odłączone</string>
<string name="pref_mobileUpdate_title">Aktualizacje mobilne</string>
<string name="pref_mobileUpdate_sum">Zezwól na aktualizacje poprzez sieć komórkową</string>
<string name="refreshing_label">Odświeżanie</string>
<string name="flattr_settings_label">Ustawienia Flattr</string>
<string name="pref_flattr_auth_title">Logowanie do Flattr</string>
<string name="pref_flattr_auth_sum">Zaloguj się do konta Flattr aby wspierać twórców bezpośrednio z aplikacji.</string>
<string name="pref_flattr_this_app_title">Wesprzyj aplikację na Flattr</string>
<string name="pref_flattr_this_app_sum">Wesprzyj twórcę AntennaPod przez Flattr. Dzięki!</string>
<string name="pref_revokeAccess_title">Anuluj dostęp</string>
<string name="pref_revokeAccess_sum">Anuluj dostęp tej aplikacji do konta Flattr </string>
<string name="pref_auto_flattr_title">Automatyczne wsparcie na Flattr</string>
<string name="pref_display_only_episodes_title">Wyświetlaj tylko odcinki</string>
<string name="pref_display_only_episodes_sum">Wyświetlaj tylko pozycje, które mają również odcinki.</string>
<string name="user_interface_label">Interfejs użytkownika</string>
<string name="pref_set_theme_title">Wybierz motyw</string>
<string name="pref_set_theme_sum">Zmień wygląd AntennaPod.</string>
<string name="pref_automatic_download_title">Automatyczne pobieranie</string>
<string name="pref_automatic_download_sum">Skonfiguruj automatyczne pobieranie odcinków.</string>
<string name="pref_autodl_wifi_filter_title">Włącz filtr Wi-Fi</string>
<string name="pref_autodl_wifi_filter_sum">Zezwól na automatyczne pobieranie tylko dla określonych sieci Wi-Fi.</string>
<string name="pref_episode_cache_title">Pamięć podręczna odcinków</string>
<string name="pref_theme_title_light">Jasny</string>
<string name="pref_theme_title_dark">Ciemny</string>
<string name="pref_episode_cache_unlimited">Nielimitowane</string>
<string name="pref_update_interval_hours_plural">godziny</string>
<string name="pref_update_interval_hours_singular">godzina</string>
<string name="pref_update_interval_hours_manual">Instrukcja</string>
<string name="pref_gpodnet_authenticate_title">Zaloguj</string>
<string name="pref_gpodnet_authenticate_sum">Zaloguj się swoim kontem na gpodder.net w celu synchronizacji Twoich subskrypcji.</string>
<string name="pref_gpodnet_logout_title">Wyloguj</string>
<string name="pref_gpodnet_logout_toast">Wylogowanie się powiodło</string>
<string name="pref_gpodnet_setlogin_information_title">Zmień informacje logowania</string>
<string name="pref_gpodnet_setlogin_information_sum">Zmień dane logowania konta gpodder.net.</string>
<string name="pref_playback_speed_title">Prędkość odtwarzania</string>
<string name="pref_playback_speed_sum">Dostosuj prędkości dostępne dla odtwarzania audio o zmiennej prędkości</string>
<string name="pref_gpodnet_sethostname_title">Ustaw nazwę hosta</string>
<string name="pref_gpodnet_sethostname_use_default_host">Użyj domyślnego hosta</string>
<!--Search-->
<string name="search_hint">Szukaj kanałów lub odcinków</string>
<string name="found_in_shownotes_label">Znaleziono w notatkach</string>
<string name="found_in_chapters_label">Znaleziono w rozdziałach</string>
<string name="search_status_searching">Wyszukiwanie...</string>
<string name="search_status_no_results">Brak wyników</string>
<string name="search_results_label">Wyniki wyszukiwania</string>
<string name="search_term_label">Szukałeś:\u0020</string>
<string name="search_label">Szukaj</string>
<string name="found_in_title_label">Znaleziono w tytułach</string>
<!--OPML import and export-->
<string name="opml_import_txtv_button_lable">Pliki OPML pozwalają na przenoszenie podcastów między aplikacjami.</string>
<string name="opml_import_explanation">W celu importu pliku OPML musisz umieścić go w poniższym folderze i nacisnąć przycisk poniżej w celu rozpoczęcia importu.</string>
<string name="start_import_label">Rozpocznij import</string>
<string name="opml_import_label">Import OPML</string>
<string name="opml_directory_error">BŁĄD!</string>
<string name="reading_opml_label">Odczytuję plik OPML</string>
<string name="opml_reader_error">Wystąpił błąd w czasie odczytu dokumentu OPML:</string>
<string name="opml_import_error_dir_empty">Katalog importowania jest pusty.</string>
<string name="select_all_label">Zaznacz wszystko</string>
<string name="deselect_all_label">Odznacz wszystko</string>
<string name="choose_file_to_import_label">Wybierz plik do importu</string>
<string name="opml_export_label">Eksport OPML</string>
<string name="exporting_label">Eksportowanie...</string>
<string name="export_error_label">Błąd eksportu</string>
<string name="opml_export_success_title">Eksport OPML udany.</string>
<string name="opml_export_success_sum">Plik .opml został zapisany do:\u0020</string>
<!--Sleep timer-->
<string name="set_sleeptimer_label">Ustaw czas do wyłączenia</string>
<string name="disable_sleeptimer_label">Wyłącz wyłącznik czasowy</string>
<string name="enter_time_here_label">Podaj czas</string>
<string name="sleep_timer_label">Wyłącznik czasowy</string>
<string name="time_left_label">Pozostały czas:\u0020</string>
<string name="time_dialog_invalid_input">Błąd wpisu, czas musi być liczbą całkowitą</string>
<!--Miro Guide-->
<string name="loading_categories_label">Ładuję kategorie...</string>
<string name="browse_miroguide_label">Przeglądaj Miro Guide</string>
<string name="txtv_browse_miroguide_label">Lub przeglądaj Miro Guide:</string>
<string name="miro_guide_label">Miro Guide</string>
<string name="miro_search_hint">Szukaj w Miro Guide</string>
<string name="popular_label">Popularne</string>
<string name="best_rating_label">Najwyższe oceny</string>
<string name="add_feed_label">Dodaj podcast</string>
<string name="miro_feed_added">Kanał jest dodawany</string>
<!--gpodder.net-->
<string name="gpodnet_taglist_header">KATEGORIE</string>
<string name="gpodnet_toplist_header">TOP PODCASTY</string>
<string name="gpodnet_suggestions_header">SUGESTIE</string>
<string name="gpodnet_search_hint">Szukaj na gpodder.net</string>
<string name="gpodnetauth_login_title">Login</string>
<string name="gpodnetauth_login_descr">Witamy w procesie logowania do gpodder.net. Najpierw podaj swoje dane logowania:</string>
<string name="gpodnetauth_login_butLabel">Login</string>
<string name="gpodnetauth_login_register">Jeśli nie masz jeszcze konta, możesz utworzyć je tutaj:\nhttps://gpodder.net/register/</string>
<string name="username_label">Nazwa użytkownika</string>
<string name="password_label">Hasło</string>
<string name="gpodnetauth_device_title">Wybór urządzenia</string>
<string name="gpodnetauth_device_descr">Utwórz nowe urządzenie dla swojego konta na gpodder.net lub wybierz istniejące:</string>
<string name="gpodnetauth_device_deviceID">Identyfikator urządzenia:\u0020</string>
<string name="gpodnetauth_device_caption">Tytuł</string>
<string name="gpodnetauth_device_butCreateNewDevice">Utwórz nowe urządzenie</string>
<string name="gpodnetauth_device_chooseExistingDevice">Wybierz istniejące urządzenie:</string>
<string name="gpodnetauth_device_errorEmpty">Identyfikator urządzenia nie może być pusty</string>
<string name="gpodnetauth_device_errorAlreadyUsed">Identyfikator urządzenia w użyciu</string>
<string name="gpodnetauth_device_butChoose">Wybierz</string>
<string name="gpodnetauth_finish_title">Logowanie zakończone sukcesem!</string>
<string name="gpodnetauth_finish_descr">Gratulacje! Twoje konto na gpodder.net jest połączone z urządzeniem. AntennaPod będzie automatycznie synchronizować subskrypcje na urządzeniu z kontem na gpodder.net. </string>
<string name="gpodnetauth_finish_butsyncnow">Rozpocznij synchronizację</string>
<string name="gpodnetauth_finish_butgomainscreen">Idź do strony głównej</string>
<string name="gpodnetsync_auth_error_title">Błąd autoryzacji na gpodder.net</string>
<string name="gpodnetsync_auth_error_descr">Niepoprawna nazwa użytkownika lub hasło</string>
<string name="gpodnetsync_error_title">Błąd synchronizacji z gpodder.net</string>
<string name="gpodnetsync_error_descr">Wystąpił błąd podczas synchronizacji:\u0020</string>
<!--Directory chooser-->
<string name="selected_folder_label">Wybrany folder:</string>
<string name="create_folder_label">Utwórz folder</string>
<string name="choose_data_directory">Wybierz folder danych</string>
<string name="create_folder_msg">Utworzyć nowy folder o nazwie \"%1$s\"?</string>
<string name="create_folder_success">Utworzono nowy folder</string>
<string name="create_folder_error_no_write_access">Nie można zapisać do tego folderu</string>
<string name="create_folder_error_already_exists">Folder już istnieje</string>
<string name="create_folder_error">Nie można utworzyć folderu</string>
<string name="folder_not_empty_dialog_title">Folder nie jest pusty</string>
<string name="folder_not_empty_dialog_msg">Wybrany folder nie jest pusty. Pobierane media i inne pliki będą umieszczane bezpośrednio w folderze, czy kontynuować?</string>
<string name="set_to_default_folder">Wybierz domyślny folder</string>
<string name="pref_pausePlaybackForFocusLoss_sum">Wstrzymaj odtwarzanie zamiast wyciszenia jeśli inna aplikacja chce odtworzyć dźwięk.</string>
<string name="pref_pausePlaybackForFocusLoss_title">Wstrzymaj przy przerwaniu</string>
<!--Online feed view-->
<string name="subscribe_label">Subskrybuj</string>
<string name="subscribed_label">Subskrybowane</string>
<string name="downloading_label">Pobieranie...</string>
<!--Content descriptions for image buttons-->
<string name="show_chapters_label">Pokaż rozdziały</string>
<string name="rewind_label">Cofnij</string>
<string name="fast_forward_label">Przewiń</string>
<string name="media_type_audio_label">Audio</string>
<string name="media_type_video_label">Wideo</string>
<string name="status_playing_label">Odcinek jest odtwarzany</string>
<string name="status_downloading_label">Odcinek jest pobierany</string>
<string name="status_downloaded_label">Odcinek pobrany</string>
<string name="status_unread_label">Nowa pozycja</string>
<string name="in_queue_label">Odcinek jest w kolejce</string>
<string name="new_episodes_count_label">Liczba nowych odcinków</string>
<string name="in_progress_episodes_count_label">Liczba odcinków, których zacząłeś słuchać</string>
</resources>

View File

@ -96,14 +96,14 @@
<string name="download_error_io_error">Erro de IO</string>
<string name="download_error_request_error">Erro de requisição</string>
<string name="download_error_db_access">Erro no acesso ao Banco de dados</string>
<string name="downloads_left">\u0020Downloads faltando</string>
<string name="downloads_left">\u0020Downloads restantes</string>
<string name="download_notification_title">Baixando dados do podcast</string>
<string name="download_report_content">%1$d downloads com sucesso, %2$d falharam</string>
<string name="download_log_title_unknown">Título desconhecido</string>
<string name="download_type_feed">Feed</string>
<string name="download_type_media">Arquivo de mídia</string>
<string name="download_type_image">Imagem</string>
<string name="download_request_error_dialog_message_prefix">Ocorreu um erro na tentativa de baixar o arquivo:\u0020</string>
<string name="download_request_error_dialog_message_prefix">Ocorreu um erro durante download do arquivo:\u0020</string>
<!--Mediaplayer messages-->
<string name="player_error_msg">Erro!</string>
<string name="player_stopped_msg">Nenhuma mídia tocando</string>
@ -140,12 +140,11 @@
<string name="action_forbidden_msg">AntennaPod não tem permissão para esta ação. A permissão de acesso do AntennaPod pode ter sido revogada. Você pode re-autenticar ou visitar o website do feed.</string>
<string name="access_revoked_title">Acesso revogado</string>
<string name="access_revoked_info">Você revogou o token de acesso do AntennaPod com sucesso. Para finalizar o processo, você deve remover esta app da lista de aplicativos aprovados nas configurações de sua conta no website do Flattr.</string>
<string name="flattr_click_success">Registrado no Flattr com sucesso!</string>
<string name="flattring_label">Registrando no Flattr</string>
<!--Flattr-->
<!--Variable Speed-->
<string name="download_plugin_label">Download Plugin</string>
<string name="no_playback_plugin_title">Plugin Não Instalado</string>
<string name="no_playback_plugin_msg">Para ajustar a velocidade de reprodução, uma biblioteca de terceiros deve ser instalada.\n\nToque em \'Download Plugin\' para baixar um plugin grátis na Play Store.\n\nQuaisquer problemas encontrados usando esse plugin não é responsabilidade do AntennaPod e deve ser reportado ao proprietário do plugin.</string>
<string name="no_playback_plugin_msg">Para velocidade variável de reprodução funcionar uma biblioteca de terceiros deve ser instalada.\n\nToque em \'Download Plugin\' para baixar um plugin grátis na Play Store.\n\nQuaisquer problemas encontrados usando esse plugin não é responsabilidade do AntennaPod e deve ser reportado ao proprietário do plugin.</string>
<string name="set_playback_speed_label">Velocidades de Reprodução</string>
<!--Empty list labels-->
<string name="no_items_label">Não existem itens nesta lista.</string>
@ -161,7 +160,7 @@
<string name="playback_pref">Reprodução</string>
<string name="network_pref">Rede</string>
<string name="pref_autoUpdateIntervall_title">Intervalo de atualização</string>
<string name="pref_autoUpdateIntervall_sum">Especifica o intervalo em que os feeds serão atualizados automaticamente ou desabilita esta funcionalidade</string>
<string name="pref_autoUpdateIntervall_sum">Especifica o intervalo com que os feeds serão atualizados automaticamente ou desabilita esta funcionalidade</string>
<string name="pref_downloadMediaOnWifiOnly_sum">Fazer download dos arquivos apenas via rede WiFi</string>
<string name="pref_followQueue_title">Reprodução contínua</string>
<string name="pref_downloadMediaOnWifiOnly_title">Download de mídia via WiFi</string>
@ -193,13 +192,13 @@
<string name="pref_update_interval_hours_singular">hora</string>
<string name="pref_update_interval_hours_manual">Manual</string>
<string name="pref_gpodnet_authenticate_title">Login</string>
<string name="pref_gpodnet_authenticate_sum">Faça o login na sua conta gpodder.net para sincronizar suas subscrições.</string>
<string name="pref_gpodnet_authenticate_sum">Faça o login na sua conta gpodder.net para sincronizar suas assinaturas.</string>
<string name="pref_gpodnet_logout_title">Sair</string>
<string name="pref_gpodnet_logout_toast">Saiu com sucesso</string>
<string name="pref_gpodnet_setlogin_information_title">Alterar informações de login</string>
<string name="pref_gpodnet_setlogin_information_sum">Alterar informações de login da sua conta gpodder.net</string>
<string name="pref_playback_speed_title">Velocidades de Reprodução</string>
<string name="pref_playback_speed_sum">Personalize as velocidades disponíveis para reprodução de áudio.</string>
<string name="pref_playback_speed_sum">Personalize as velocidades variáveis de reprodução de áudio.</string>
<string name="pref_gpodnet_sethostname_title">Configurar hostname</string>
<string name="pref_gpodnet_sethostname_use_default_host">Usar host padrão</string>
<!--Search-->
@ -213,7 +212,7 @@
<string name="search_label">Pesquisar</string>
<string name="found_in_title_label">Encontrado no título</string>
<!--OPML import and export-->
<string name="opml_import_txtv_button_lable">Arquivos OPML permitem que você mova seus podcasts de um gestor de podcasts para outro.</string>
<string name="opml_import_txtv_button_lable">Arquivos OPML permitem que você mova seus podcasts de um programa de podcasts para outro.</string>
<string name="opml_import_explanation">Para importar um arquivo OPML, você precisa armazená-lo neste diretório e pressionar o botão abaixo para iniciar o processo de importação.</string>
<string name="start_import_label">Iniciar importação</string>
<string name="opml_import_label">Importação de OPML</string>
@ -231,9 +230,9 @@
<string name="opml_export_success_sum">O arquivo .opml foi gravado em:\u0020</string>
<!--Sleep timer-->
<string name="set_sleeptimer_label">Configura desligamento automático</string>
<string name="disable_sleeptimer_label">Desabilita temporizador</string>
<string name="disable_sleeptimer_label">Desabilita desligamento automático</string>
<string name="enter_time_here_label">Informe a duração</string>
<string name="sleep_timer_label">Temporizador</string>
<string name="sleep_timer_label">Desligamento automático</string>
<string name="time_left_label">Tempo restante:\u0020</string>
<string name="time_dialog_invalid_input">Entrada inválida, a duração precisa ser um número inteiro</string>
<!--Miro Guide-->
@ -250,6 +249,7 @@
<string name="gpodnet_taglist_header">CATEGORIAS</string>
<string name="gpodnet_toplist_header">TOP PODCASTS</string>
<string name="gpodnet_suggestions_header">SUGESTÕES</string>
<string name="gpodnet_search_hint">Buscar no gpodder.net</string>
<string name="gpodnetauth_login_title">Login</string>
<string name="gpodnetauth_login_descr">Bem-vindo ao processo de login gpodder.net. Primeiramente, digite suas informações:</string>
<string name="gpodnetauth_login_butLabel">Login</string>
@ -266,12 +266,12 @@
<string name="gpodnetauth_device_errorAlreadyUsed">ID do dispositivo já está em uso</string>
<string name="gpodnetauth_device_butChoose">Escolher</string>
<string name="gpodnetauth_finish_title">Login realizado com sucesso!</string>
<string name="gpodnetauth_finish_descr">Parabéns! Sua conta gpodder.net agora está ligada ao seu dispositivo. AntennaPod irá, daqui em diante, sincronizar automaticamente subscrições do seu dispositivo com sua conta gpodder.net.</string>
<string name="gpodnetauth_finish_descr">Parabéns! Sua conta gpodder.net agora está conectada ao seu dispositivo. O AntennaPod irá, daqui em diante, sincronizar automaticamente assinaturas do seu dispositivo com sua conta gpodder.net.</string>
<string name="gpodnetauth_finish_butsyncnow">Iniciar sincronização agora</string>
<string name="gpodnetauth_finish_butgomainscreen">Ir para tela principal</string>
<string name="gpodnetsync_auth_error_title">gpodder.net: Erro de autenticação</string>
<string name="gpodnetsync_auth_error_title">gpodder.net: erro de autenticação</string>
<string name="gpodnetsync_auth_error_descr">Nome do usuário ou senha incorreta</string>
<string name="gpodnetsync_error_title">gpodder.net: Erro de sincronização</string>
<string name="gpodnetsync_error_title">gpodder.net: erro de sincronização</string>
<string name="gpodnetsync_error_descr">Ocorreu um erro durante a sincronização:\u0020</string>
<!--Directory chooser-->
<string name="selected_folder_label">Selecionar pasta:</string>
@ -285,10 +285,18 @@
<string name="folder_not_empty_dialog_title">A pasta não está vazia</string>
<string name="folder_not_empty_dialog_msg">A pasta que você selecionou não está vazia. Os downloads de mídia e outros arquivos serão colocados diretamente nesta pasta. Deseja mesmo continuar?</string>
<string name="set_to_default_folder">Escolher pasta padrão</string>
<string name="pref_pausePlaybackForFocusLoss_sum">Pause a reprodução em vez de baixar o volume quando outro aplicativo reproduzir sons</string>
<string name="pref_pausePlaybackForFocusLoss_sum">Pause a reprodução em vez de abaixar o volume quando outro aplicativo reproduzir sons</string>
<string name="pref_pausePlaybackForFocusLoss_title">Pausar em interrupções</string>
<!--Online feed view-->
<string name="subscribe_label">Subscrever</string>
<string name="subscribed_label">Subscrito</string>
<string name="subscribe_label">Assinar</string>
<string name="subscribed_label">Assinado</string>
<string name="downloading_label">Baixando...</string>
<!--Content descriptions for image buttons-->
<string name="show_cover_label">Mostrar imagem</string>
<string name="butAction_label">Mais ações</string>
<string name="status_playing_label">Episódio está sendo reproduzido</string>
<string name="status_downloaded_label">Episódio foi baixado</string>
<string name="status_unread_label">Item é novo</string>
<string name="in_queue_label">Episódio está na fila</string>
<string name="new_episodes_count_label">Numero de novos episódios</string>
</resources>

View File

@ -140,12 +140,22 @@
<string name="action_forbidden_msg">O AntennaPod não possui as permissões para esta ação. É possível que o token de acesso ao flattr via AntennaPod tenha sido revogado. Pode efetuar nova autenticação ou aceder ao sítio web do item.</string>
<string name="access_revoked_title">Acesso revogado</string>
<string name="access_revoked_info">Você revogou o token de acesso do AntennaPod à sua conta. Para concluir o processo, tem que remover esta aplicação da lista de aplicações presentes nas definições de conta no sítio web do flattr.</string>
<string name="flattr_click_success">Flattered com sucesso!</string>
<string name="flattring_label">Flattring</string>
<!--Flattr-->
<string name="flattr_click_success">Flattr de um item!</string>
<string name="flattr_click_success_count">Flattr de %d itens!</string>
<string name="flattr_click_success_queue">Flattr: %s</string>
<string name="flattr_click_failure_count">Falha ao efetuar flattr de %d itens!</string>
<string name="flattr_click_failure">Não flattr: %s.</string>
<string name="flattr_click_enqueued">O flattr deste item será feito mais tarde</string>
<string name="flattring_thing">Flattring %s</string>
<string name="flattring_label">O AntennaPod está a flattring</string>
<string name="flattrd_label">O AntennaPod fez o flattr</string>
<string name="flattrd_failed_label">O AntennaPod não fez o flattr</string>
<string name="flattr_retrieving_status">A obter itens com flattr</string>
<!--Variable Speed-->
<string name="download_plugin_label">Transferir extra</string>
<string name="no_playback_plugin_title">Extra não instalado</string>
<string name="no_playback_plugin_msg">Para melhorar a reprodução, deve transferir e instalar um biblioteca de terceiros.\nClique Transferir extra para transferir o extra através da loja Google..\n\nSe encontrar problemas ao utilizar esta biblioteca, os programadores do AntennaPod não podem ser responsabilizados e deve contactar o programador do extra.</string>
<string name="no_playback_plugin_msg">Para melhorar a reprodução, deve transferir e instalar um biblioteca de terceiros.\nClique Transferir extra para transferir o extra através da loja Google.\n\nSe encontrar problemas ao utilizar esta biblioteca, os programadores do AntennaPod não podem ser responsabilizados e deve contactar o programador do extra.</string>
<string name="set_playback_speed_label">Velocidades de reprodução</string>
<!--Empty list labels-->
<string name="no_items_label">Não existem itens na lista.</string>
@ -176,10 +186,12 @@
<string name="pref_flattr_this_app_sum">Ajude no desenvolvimento do AntennaPod através do Flattr. Obrigado!</string>
<string name="pref_revokeAccess_title">Revogar acesso</string>
<string name="pref_revokeAccess_sum">Revogar permissões de acesso da aplicação à sua conta flattr.</string>
<string name="pref_auto_flattr_title">Flattr automático</string>
<string name="pref_auto_flattr_sum">Flattr de episódios com 80 porcento de reprodução.</string>
<string name="pref_display_only_episodes_title">Mostrar apenas episódios</string>
<string name="pref_display_only_episodes_sum">Apenas mostrar itens que possuam episódios.</string>
<string name="user_interface_label">Interface</string>
<string name="pref_set_theme_title">Escolha o tema</string>
<string name="pref_set_theme_title">Tema</string>
<string name="pref_set_theme_sum">Mudar o aspeto do AntennaPod.</string>
<string name="pref_automatic_download_title">Transferência automática</string>
<string name="pref_automatic_download_sum">Configure a transferência automática dos episódios.</string>
@ -292,4 +304,21 @@
<string name="subscribe_label">Subscrever</string>
<string name="subscribed_label">Subscrito</string>
<string name="downloading_label">Transferência...</string>
<!--Content descriptions for image buttons-->
<string name="show_chapters_label">Mostrar capítulos</string>
<string name="show_shownotes_label">Mostrar notas</string>
<string name="show_cover_label">Mostrar imagem</string>
<string name="rewind_label">Recuar</string>
<string name="fast_forward_label">Avanço rápido</string>
<string name="media_type_audio_label">Áudio</string>
<string name="media_type_video_label">Vídeo</string>
<string name="navigate_upwards_label">Navegar para cima</string>
<string name="butAction_label">Mais ações</string>
<string name="status_playing_label">Episódio em reprodução</string>
<string name="status_downloading_label">Episódio a ser transferido</string>
<string name="status_downloaded_label">Episódio transferido</string>
<string name="status_unread_label">Novo item</string>
<string name="in_queue_label">Episódio está na fila</string>
<string name="new_episodes_count_label">Número de novos episódios</string>
<string name="in_progress_episodes_count_label">Número de episódios que já foi iniciada a reprodução</string>
</resources>

View File

@ -27,7 +27,6 @@
<string name="author_label">Autor</string>
<string name="language_label">Limbă</string>
<string name="podcast_settings_label">Setări</string>
<string name="cover_label">Copertă</string>
<string name="error_label">Eroare</string>
<string name="error_msg_prefix">A avut loc o eroare:</string>
<string name="refresh_label">Reîncarcă</string>
@ -132,8 +131,7 @@
<string name="action_forbidden_msg">AntennaPod nu are permisiuni pentru această acțiune. Motivul poate fi că tokenul de acces al AntennaPod pentru contul vostru a fost revocat. Vă puteți fie re-autentifica fie vizita direct site-ul.</string>
<string name="access_revoked_title">Acces revocat</string>
<string name="access_revoked_info">Ați revocat cu succes accesul AntennaPod la contul vostru. Pentru a completa acest proces trebuie să ștergeți aplicația din lista de aplicații aprobate din setările contului de pe site-ul flattr.</string>
<string name="flattr_click_success">Ați flattr cu succes!</string>
<string name="flattring_label">Flattring</string>
<!--Flattr-->
<!--Variable Speed-->
<string name="download_plugin_label">Descarcă plugin</string>
<string name="no_playback_plugin_title">Plugin neinstalat</string>
@ -263,4 +261,5 @@
<string name="subscribe_label">Abonează-te</string>
<string name="subscribed_label">Abonat</string>
<string name="downloading_label">Se descarcă...</string>
<!--Content descriptions for image buttons-->
</resources>

View File

@ -28,16 +28,16 @@
<string name="author_label">Автор</string>
<string name="language_label">Язык</string>
<string name="podcast_settings_label">Настройки</string>
<string name="cover_label">Обложка</string>
<string name="cover_label">Изображение</string>
<string name="error_label">Ошибка</string>
<string name="error_msg_prefix">Произошла ошибка:</string>
<string name="refresh_label">Обновить</string>
<string name="external_storage_error_msg">Внешний носитель недоступен. Убедитесь что внешний носитель установлен, иначе приложение не сможет нормально работать.</string>
<string name="chapters_label">Разделы</string>
<string name="shownotes_label">Описание</string>
<string name="shownotes_label">Заметки к эпизоду</string>
<string name="description_label">Описание</string>
<string name="most_recent_prefix">Следующий эпизод:\u0020</string>
<string name="episodes_suffix">\u0020 выпуск(ов)</string>
<string name="episodes_suffix">\u0020выпуск(ов)</string>
<string name="published_prefix">Опубликовано:\u0020</string>
<string name="length_prefix">Продолжительность:\u0020</string>
<string name="size_prefix">Размер:\u0020</string>
@ -56,8 +56,8 @@
<string name="mark_all_read_label">Отметить все как прочитанное</string>
<string name="show_info_label">Показать информацию</string>
<string name="remove_feed_label">Удалить канал</string>
<string name="share_link_label">Поделиться ссылкой на сайт</string>
<string name="share_source_label">Поделиться ссылкой на канал</string>
<string name="share_link_label">Ссылка на сайт</string>
<string name="share_source_label">Ссылка на канал</string>
<string name="feed_delete_confirmation_msg">Подтвердите удаление канала и ВСЕХ загруженных с этого канала выпусков.</string>
<string name="feed_remover_msg">Удаление канала</string>
<!--actions on feeditems-->
@ -81,7 +81,7 @@
<string name="download_pending">Загрузка в ожидании</string>
<string name="download_running">Загрузка в процессе</string>
<string name="download_error_device_not_found">Устройство хранения не найдено</string>
<string name="download_error_insufficient_space">Недостаточно памяти</string>
<string name="download_error_insufficient_space">Недостаточно места</string>
<string name="download_error_file_error">Ошибка файла</string>
<string name="download_error_http_data_error">Ошибка протокола HTTP</string>
<string name="download_error_error_unknown">Неизвестная ошибка</string>
@ -140,8 +140,8 @@
<string name="action_forbidden_msg">AntennaPod не имеет прав для выполнения этого действия. Возможно, доступ к вашему аккаунту был отозван. Вы можете авторизоваться заново или посетить сайт, которому вы пожертвовали через Flattr.</string>
<string name="access_revoked_title">Доступ отозван</string>
<string name="access_revoked_info">Вы успешно отключили AntennaPod от вашего аккаунта в Flattr. Чтобы завершить этот процесс вам нужно удалить AntennaPod из списка приложений подключенных к вашему аккаунту на сайте Flattr.</string>
<string name="flattr_click_success">Поддержка через Flattr прошла успешно!</string>
<string name="flattring_label">Отправляется запрос на Flattr</string>
<!--Flattr-->
<string name="flattr_click_success_queue">Поддержано через Flattr: %s.</string>
<!--Variable Speed-->
<string name="download_plugin_label">Загрузить плагин</string>
<string name="no_playback_plugin_title">Плагин не установлен</string>
@ -176,6 +176,8 @@
<string name="pref_flattr_this_app_sum">Поддержите разработку AntennaPod через Flattr. Спасибо!</string>
<string name="pref_revokeAccess_title">Отозвать доступ</string>
<string name="pref_revokeAccess_sum">Отменить доступ этого приложения к вашему аккаунту Flattr.</string>
<string name="pref_auto_flattr_title">Автоматически поддерживать через Flattr</string>
<string name="pref_auto_flattr_sum">Поддерживать через Flattr эпизоды, прослушанные на 80%</string>
<string name="pref_display_only_episodes_title">Показывать только выпуски</string>
<string name="pref_display_only_episodes_sum">Показывать только те элементы списка, которые содержат выпуски</string>
<string name="user_interface_label">Интерфейс</string>
@ -189,8 +191,8 @@
<string name="pref_theme_title_light">Светлая</string>
<string name="pref_theme_title_dark">Тёмная</string>
<string name="pref_episode_cache_unlimited">Неограничен</string>
<string name="pref_update_interval_hours_plural">Часы</string>
<string name="pref_update_interval_hours_singular">Час</string>
<string name="pref_update_interval_hours_plural">ч.</string>
<string name="pref_update_interval_hours_singular">час</string>
<string name="pref_update_interval_hours_manual">Вручную</string>
<string name="pref_gpodnet_authenticate_title">Вход в gpodder.net</string>
<string name="pref_gpodnet_authenticate_sum">Вход в ваш аккаунт gpodder.net для синхронизации ваших подписок.</string>
@ -258,7 +260,7 @@
<string name="username_label">Имя пользователя</string>
<string name="password_label">Пароль</string>
<string name="gpodnetauth_device_title">Выбор устройства</string>
<string name="gpodnetauth_device_descr">Создайте новое устройство, чтобы использовать ваш аккаунта на gpodder.net или выберите существующее:</string>
<string name="gpodnetauth_device_descr">Создайте новое устройство, чтобы использовать ваш аккаунт на gpodder.net или выберите существующее:</string>
<string name="gpodnetauth_device_deviceID">Device ID:\u0020</string>
<string name="gpodnetauth_device_caption">Название устройства</string>
<string name="gpodnetauth_device_butCreateNewDevice">Создайте новое устройство</string>
@ -268,12 +270,12 @@
<string name="gpodnetauth_device_butChoose">Выберите</string>
<string name="gpodnetauth_finish_title">Авторизация успешна!</string>
<string name="gpodnetauth_finish_descr">Поздравляем! Ваш аккаунт на gpodder.net теперь связан с вашим устройством. AntennaPod теперь сможет автоматически синхронизировать ваши подписки с аккаунтом gpodder.net</string>
<string name="gpodnetauth_finish_butsyncnow">Начать синхронизацию сейчас</string>
<string name="gpodnetauth_finish_butsyncnow">Начать синхронизацию</string>
<string name="gpodnetauth_finish_butgomainscreen">Перейти на главный экран</string>
<string name="gpodnetsync_auth_error_title">Ошибка авторизации на gpodder.net</string>
<string name="gpodnetsync_auth_error_descr">Неправильное имя пользователя или пароль</string>
<string name="gpodnetsync_error_title">Ошибка синхронизации с gpodder.net</string>
<string name="gpodnetsync_error_descr">Произошла ошибка во время синзронизации:\u0020</string>
<string name="gpodnetsync_error_descr">Произошла ошибка во время синхронизации:\u0020</string>
<!--Directory chooser-->
<string name="selected_folder_label">Выбранная папка:</string>
<string name="create_folder_label">Создать папку</string>
@ -292,4 +294,21 @@
<string name="subscribe_label">Подписаться</string>
<string name="subscribed_label">Подписка оформлена</string>
<string name="downloading_label">Загрузка...</string>
<!--Content descriptions for image buttons-->
<string name="show_chapters_label">Показать разделы</string>
<string name="show_shownotes_label">Показать заметки к эпизодам</string>
<string name="show_cover_label">Показать изображение</string>
<string name="rewind_label">Назад</string>
<string name="fast_forward_label">Вперед</string>
<string name="media_type_audio_label">Аудио</string>
<string name="media_type_video_label">Видео</string>
<string name="navigate_upwards_label">Перейти выше</string>
<string name="butAction_label">Другие действия</string>
<string name="status_playing_label">Эпизод воспроизводится</string>
<string name="status_downloading_label">Эпизод загружается</string>
<string name="status_downloaded_label">Эпизод загружен</string>
<string name="status_unread_label">Новый</string>
<string name="in_queue_label">Эпизод в очереди</string>
<string name="new_episodes_count_label">Количество новых эпизодов</string>
<string name="in_progress_episodes_count_label">Количество начатых эпизодов</string>
</resources>

View File

@ -28,7 +28,7 @@
<string name="author_label">Skapare</string>
<string name="language_label">Språk</string>
<string name="podcast_settings_label">Inställningar</string>
<string name="cover_label">Omslag</string>
<string name="cover_label">Bild</string>
<string name="error_label">Fel</string>
<string name="error_msg_prefix">Ett fel inträffade:</string>
<string name="refresh_label">Uppdatera</string>
@ -118,7 +118,7 @@
<string name="playbackservice_notification_title">Spelar podcast</string>
<string name="playbackservice_notification_content">Tryck här för mer information</string>
<!--Navigation-->
<string name="show_download_log">Visa log</string>
<string name="show_download_log">Visa logg</string>
<string name="show_player_label">Visa spelare</string>
<!--Queue operations-->
<string name="clear_queue_label">Rensa kön</string>
@ -140,8 +140,18 @@
<string name="action_forbidden_msg">AntennaPod saknar behörighet för den här åtgärden. Anledningen till detta kan vara att AntennaPods tillgång till ditt konto har återkallats. Du kan antingen åter autentisera AntennaPod eller besöka hemsidan istället.</string>
<string name="access_revoked_title">Tillgång återkallad</string>
<string name="access_revoked_info">Du har nu återkallat AntennaPods tillgång till ditt konto. För att slutföra processen, måste du ta bort den här appen från listan godkända appar i dina kontoinställningar på Flattrs hemsida.</string>
<string name="flattr_click_success">Framgångsrikt flattrat denna sak!</string>
<string name="flattring_label">Flattrar</string>
<!--Flattr-->
<string name="flattr_click_success">Flattrade en sak!</string>
<string name="flattr_click_success_count">Flattrade %d saker!</string>
<string name="flattr_click_success_queue">Flattrade: %s.</string>
<string name="flattr_click_failure_count">Misslyckades att flattra %d saker!</string>
<string name="flattr_click_failure">Ej flattrade: %s.</string>
<string name="flattr_click_enqueued">Saker som kommer att flattras senare</string>
<string name="flattring_thing">Flattrar %s</string>
<string name="flattring_label">AnntennaPod flattrar</string>
<string name="flattrd_label">AntennaPod har flattrat</string>
<string name="flattrd_failed_label">AntennaPod misslyckades att flattra</string>
<string name="flattr_retrieving_status">Hämtar flattrade saker</string>
<!--Variable Speed-->
<string name="download_plugin_label">Ladda ner tillägg</string>
<string name="no_playback_plugin_title">Tillägg ej installerat</string>
@ -176,6 +186,8 @@
<string name="pref_flattr_this_app_sum">Stöd utvecklingen av AntennaPod genom att flattra den. Tack!</string>
<string name="pref_revokeAccess_title">Återkalla åtkomst</string>
<string name="pref_revokeAccess_sum">Återkalla behörigheten till ditt Flattr-konto för denna app.</string>
<string name="pref_auto_flattr_title">Automatisk Flattring</string>
<string name="pref_auto_flattr_sum">Flattra episoder som har spelats minst 80%.</string>
<string name="pref_display_only_episodes_title">Visa endast episoder</string>
<string name="pref_display_only_episodes_sum">Visa endast objekt som har minst ett avsnitt.</string>
<string name="user_interface_label">Användargränssnitt</string>
@ -292,4 +304,21 @@
<string name="subscribe_label">Prenumerera</string>
<string name="subscribed_label">Prenumererar</string>
<string name="downloading_label">Laddar ner...</string>
<!--Content descriptions for image buttons-->
<string name="show_chapters_label">Visa kapitel</string>
<string name="show_shownotes_label">Visa shownotes</string>
<string name="show_cover_label">Visa bild</string>
<string name="rewind_label">Backa</string>
<string name="fast_forward_label">Snabbspola</string>
<string name="media_type_audio_label">Ljud</string>
<string name="media_type_video_label">Video</string>
<string name="navigate_upwards_label">Navigera upp</string>
<string name="butAction_label">Fler åtgärder</string>
<string name="status_playing_label">Episoden spelas</string>
<string name="status_downloading_label">Episoden laddas ner</string>
<string name="status_downloaded_label">Episoden är nedladdad</string>
<string name="status_unread_label">Föremålet är nytt</string>
<string name="in_queue_label">Episoden är i kön</string>
<string name="new_episodes_count_label">Antal nya episoder</string>
<string name="in_progress_episodes_count_label">Antal episoder du har börjat lyssna på</string>
</resources>

View File

@ -28,7 +28,7 @@
<string name="author_label">Автор</string>
<string name="language_label">Мова</string>
<string name="podcast_settings_label">Налаштування</string>
<string name="cover_label">Обкладинка</string>
<string name="cover_label">Зображення</string>
<string name="error_label">Помилка</string>
<string name="error_msg_prefix">Трапилась помілка:</string>
<string name="refresh_label">Оновити</string>
@ -140,8 +140,18 @@
<string name="action_forbidden_msg">AntennaPod не маэ дозвілу це зробити. Можливо відкликаний доступ до AntennaPod. Або ввідіть логін пароль в налаштуваннях або зробить це на сайті</string>
<string name="access_revoked_title">Доступ відкликано</string>
<string name="access_revoked_info">Ви відкликали доступ AntennaPod до облікового запису. Для закінчення процессу вам потрібно видалити додаток з затвержденного списку в вашому облікову запису на сайті flattr</string>
<string name="flattr_click_success">Успішно flattr це</string>
<string name="flattring_label">Йде flattr</string>
<!--Flattr-->
<string name="flattr_click_success">Flattr\'ed one thing!</string>
<string name="flattr_click_success_count">Flattr\'ed %d things!</string>
<string name="flattr_click_success_queue">Flattr\'ed: %s.</string>
<string name="flattr_click_failure_count">Failed to flattr %d things!</string>
<string name="flattr_click_failure">Not flattr\'ed: %s.</string>
<string name="flattr_click_enqueued">Thing will be flattr\'ed later</string>
<string name="flattring_thing">Flattring %s</string>
<string name="flattring_label">AntennaPod is flattring</string>
<string name="flattrd_label">AntennaPod has flattr\'ed</string>
<string name="flattrd_failed_label">AntennaPod flattr failed</string>
<string name="flattr_retrieving_status">Retrieving flattr\'ed things</string>
<!--Variable Speed-->
<string name="download_plugin_label">Завантажити Plugin</string>
<string name="no_playback_plugin_title">Plugin не встановлено</string>
@ -176,6 +186,8 @@
<string name="pref_flattr_this_app_sum">Підтримайте розробку AntennaPod за допомогою flattr. Дякую!</string>
<string name="pref_revokeAccess_title">Відкликати доступ</string>
<string name="pref_revokeAccess_sum">Відкликати дозвіл на доступ до вашого flattr з цього додатку</string>
<string name="pref_auto_flattr_title">Automatic Flattr</string>
<string name="pref_auto_flattr_sum">Flattr episodes of which 80% have been played.</string>
<string name="pref_display_only_episodes_title">Показувати тільки епізоди</string>
<string name="pref_display_only_episodes_sum">Відображати тільки канали з наявними епізодами</string>
<string name="user_interface_label">Зовнішній вид</string>
@ -248,7 +260,7 @@
<string name="miro_feed_added">Канал додано</string>
<!--gpodder.net-->
<string name="gpodnet_taglist_header">КАТЕГОРІЇ</string>
<string name="gpodnet_toplist_header">ТОП ПОКАДСТІВ</string>
<string name="gpodnet_toplist_header">ТОП ПОДКАСТІВ</string>
<string name="gpodnet_suggestions_header">РЕКОМЕНДАЦІЇ</string>
<string name="gpodnet_search_hint">Пошук на gpodder.net</string>
<string name="gpodnetauth_login_title">Логін</string>
@ -292,4 +304,21 @@
<string name="subscribe_label">Підписатися</string>
<string name="subscribed_label">Підписано</string>
<string name="downloading_label">Завантаження...</string>
<!--Content descriptions for image buttons-->
<string name="show_chapters_label">Показати глави</string>
<string name="show_shownotes_label">Показати нотатки</string>
<string name="show_cover_label">Показати зображення</string>
<string name="rewind_label">Перемотка назад</string>
<string name="fast_forward_label">Перемотка вперед</string>
<string name="media_type_audio_label">Звук</string>
<string name="media_type_video_label">Відео</string>
<string name="navigate_upwards_label">Догори</string>
<string name="butAction_label">Додаткові дії</string>
<string name="status_playing_label">Епізод програється</string>
<string name="status_downloading_label">Епізод завантажується</string>
<string name="status_downloaded_label">Епізод завантажено</string>
<string name="status_unread_label">Нове</string>
<string name="in_queue_label">Епізод чекає в черзі</string>
<string name="new_episodes_count_label">Кількість нових епізодів</string>
<string name="in_progress_episodes_count_label">Кількість епізодів що ви почали слухати</string>
</resources>

View File

@ -28,7 +28,7 @@
<string name="author_label">作者</string>
<string name="language_label">语言</string>
<string name="podcast_settings_label">设置</string>
<string name="cover_label">封面</string>
<string name="cover_label">图片</string>
<string name="error_label">错误</string>
<string name="error_msg_prefix">出错:</string>
<string name="refresh_label">刷新</string>
@ -140,8 +140,7 @@
<string name="action_forbidden_msg">AntennaPod 没有权限执行本动作. 原因可能是: AntennaPod 对您账户的访问令牌被撤销. 你可以重新\"验证\"或访问该网站来授权.</string>
<string name="access_revoked_title">撤销访问</string>
<string name="access_revoked_info">您已经成功撤销 AntennaPod 对账户令牌的访问. 为了完成这个过程, 您必须到 Flattr 网站 \"账户设置-&gt;已批准应用\" 列表内删除本应用.</string>
<string name="flattr_click_success">Flattr 成功!</string>
<string name="flattring_label">Flattring</string>
<!--Flattr-->
<!--Variable Speed-->
<string name="download_plugin_label">插件下载</string>
<string name="no_playback_plugin_title">插件没有安装</string>
@ -153,7 +152,7 @@
<!--Preferences-->
<string name="other_pref">其他</string>
<string name="about_pref">关于</string>
<string name="queue_label">清空播放</string>
<string name="queue_label">播放列表</string>
<string name="services_label">服务</string>
<string name="flattr_label">Flattr</string>
<string name="pref_pauseOnHeadsetDisconnect_sum">耳机断开时暂停播放 </string>
@ -292,4 +291,21 @@
<string name="subscribe_label">订阅</string>
<string name="subscribed_label">已订阅</string>
<string name="downloading_label">下载中...</string>
<!--Content descriptions for image buttons-->
<string name="show_chapters_label">显示章节</string>
<string name="show_shownotes_label">显示笔记</string>
<string name="show_cover_label">显示图片</string>
<string name="rewind_label">回放</string>
<string name="fast_forward_label">快进</string>
<string name="media_type_audio_label">音频</string>
<string name="media_type_video_label">视频</string>
<string name="navigate_upwards_label">向上导航</string>
<string name="butAction_label">更多动作</string>
<string name="status_playing_label">曲目正在播放</string>
<string name="status_downloading_label">曲目正在下载</string>
<string name="status_downloaded_label">曲目已下载</string>
<string name="status_unread_label">新项目</string>
<string name="in_queue_label">曲目已经在播放列表中</string>
<string name="new_episodes_count_label">新曲目数</string>
<string name="in_progress_episodes_count_label">已收听曲目数</string>
</resources>

View File

@ -35,7 +35,7 @@
<string name="author_label">Author</string>
<string name="language_label">Language</string>
<string name="podcast_settings_label">Settings</string>
<string name="cover_label">Cover</string>
<string name="cover_label">Picture</string>
<string name="error_label">Error</string>
<string name="error_msg_prefix">An error occurred:</string>
<string name="refresh_label">Refresh</string>
@ -154,9 +154,20 @@
<string name="action_forbidden_msg">AntennaPod has no permission for this action. The reason for this could be that the access token of AntennaPod to your account has been revoked. You can either re-reauthenticate or visit the website of the thing instead.</string>
<string name="access_revoked_title">Access revoked</string>
<string name="access_revoked_info">You have successfully revoked AntennaPod\'s access token to your account. In order to complete the process, you have to remove this app from the list of approved applications in your account settings on the flattr website.</string>
<string name="flattr_click_success">Successfully flattred this thing!</string>
<string name="flattring_label">Flattring</string>
<!-- Flattr -->
<string name="flattr_click_success">Flattr\'ed one thing!</string>
<string name="flattr_click_success_count">Flattr\'ed %d things!</string>
<string name="flattr_click_success_queue">Flattr\'ed: %s.</string>
<string name="flattr_click_failure_count">Failed to flattr %d things!</string>
<string name="flattr_click_failure">Not flattr\'ed: %s.</string>
<string name="flattr_click_enqueued">Thing will be flattr\'ed later</string>
<string name="flattring_thing">Flattring %s</string>
<string name="flattring_label">AntennaPod is flattring</string>
<string name="flattrd_label">AntennaPod has flattr\'ed</string>
<string name="flattrd_failed_label">AntennaPod flattr failed</string>
<string name="flattr_retrieving_status">Retrieving flattr\'ed things</string>
<!-- Variable Speed -->
<string name="download_plugin_label">Download Plugin</string>
<string name="no_playback_plugin_title">Plugin Not Installed</string>
@ -193,6 +204,8 @@
<string name="pref_flattr_this_app_sum">Support the development of AntennaPod by flattring it. Thanks!</string>
<string name="pref_revokeAccess_title">Revoke access</string>
<string name="pref_revokeAccess_sum">Revoke the access permission to your flattr account for this app.</string>
<string name="pref_auto_flattr_title">Automatic Flattr</string>
<string name="pref_auto_flattr_sum">Flattr episodes of which 80% have been played.</string>
<string name="pref_display_only_episodes_title">Display only episodes</string>
<string name="pref_display_only_episodes_sum">Display only items which also have an episode.</string>
<string name="user_interface_label">User Interface</string>
@ -319,4 +332,22 @@
<string name="subscribe_label">Subscribe</string>
<string name="subscribed_label">Subscribed</string>
<string name="downloading_label">Downloading...</string>
<!-- Content descriptions for image buttons -->
<string name="show_chapters_label">Show chapters</string>
<string name="show_shownotes_label">Show shownotes</string>
<string name="show_cover_label">Show picture</string>
<string name="rewind_label">Rewind</string>
<string name="fast_forward_label">Fast forward</string>
<string name="media_type_audio_label">Audio</string>
<string name="media_type_video_label">Video</string>
<string name="navigate_upwards_label">Navigate upwards</string>
<string name="butAction_label">More actions</string>
<string name="status_playing_label">Episode is being played</string>
<string name="status_downloading_label">Episode is being downloaded</string>
<string name="status_downloaded_label">Episode is downloaded</string>
<string name="status_unread_label">Item is new</string>
<string name="in_queue_label">Episode is in the queue</string>
<string name="new_episodes_count_label">Number of new episodes</string>
<string name="in_progress_episodes_count_label">Number of episodes you have started listening to</string>
</resources>

View File

@ -83,6 +83,12 @@
<intent android:action=".activities.FlattrAuthActivity"/>
</PreferenceScreen>
<CheckBoxPreference
android:defaultValue="false"
android:enabled="false"
android:key="pref_auto_flattr"
android:summary="@string/pref_auto_flattr_sum"
android:title="@string/pref_auto_flattr_title" />
<Preference
android:key="prefRevokeAccess"
android:summary="@string/pref_revokeAccess_sum"
@ -109,7 +115,6 @@
android:key="pref_gpodnet_hostname"
android:title="@string/pref_gpodnet_sethostname_title"/>
</PreferenceScreen>
</PreferenceCategory>
<PreferenceCategory android:title="@string/other_pref">
<Preference

View File

@ -4,5 +4,5 @@ public final class AppConfig {
/** Should be used for debug logging. */
public final static boolean DEBUG = true;
/** Should be used when setting User-Agent header for HTTP-requests. */
public final static String USER_AGENT = "AntennaPod/0.9.8.0";
public final static String USER_AGENT = "AntennaPod/0.9.8.1";
}

View File

@ -11,15 +11,10 @@ import android.support.v4.app.ListFragment;
import android.util.Log;
import android.view.View;
import android.view.View.OnClickListener;
import android.view.Window;
import android.view.View.OnLongClickListener;
import android.widget.ArrayAdapter;
import android.widget.Button;
import android.widget.ImageButton;
import android.view.Window;
import android.widget.*;
import android.widget.ImageView.ScaleType;
import android.widget.ListView;
import android.widget.TextView;
import de.danoeh.antennapod.AppConfig;
import de.danoeh.antennapod.R;
import de.danoeh.antennapod.adapter.ChapterListAdapter;
@ -31,501 +26,525 @@ import de.danoeh.antennapod.feed.SimpleChapter;
import de.danoeh.antennapod.fragment.CoverFragment;
import de.danoeh.antennapod.fragment.ItemDescriptionFragment;
import de.danoeh.antennapod.preferences.UserPreferences;
import de.danoeh.antennapod.preferences.UserPreferences;
import de.danoeh.antennapod.service.PlaybackService;
import de.danoeh.antennapod.service.playback.PlaybackService;
import de.danoeh.antennapod.util.playback.ExternalMedia;
import de.danoeh.antennapod.util.playback.Playable;
/** Activity for playing audio files. */
/**
* Activity for playing audio files.
*/
public class AudioplayerActivity extends MediaplayerActivity {
private static final int POS_COVER = 0;
private static final int POS_DESCR = 1;
private static final int POS_CHAPTERS = 2;
private static final int NUM_CONTENT_FRAGMENTS = 3;
private static final int POS_COVER = 0;
private static final int POS_DESCR = 1;
private static final int POS_CHAPTERS = 2;
private static final int NUM_CONTENT_FRAGMENTS = 3;
final String TAG = "AudioplayerActivity";
private static final String PREFS = "AudioPlayerActivityPreferences";
private static final String PREF_KEY_SELECTED_FRAGMENT_POSITION = "selectedFragmentPosition";
private static final String PREF_PLAYABLE_ID = "playableId";
final String TAG = "AudioplayerActivity";
private static final String PREFS = "AudioPlayerActivityPreferences";
private static final String PREF_KEY_SELECTED_FRAGMENT_POSITION = "selectedFragmentPosition";
private static final String PREF_PLAYABLE_ID = "playableId";
private Fragment[] detachedFragments;
private Fragment[] detachedFragments;
private CoverFragment coverFragment;
private ItemDescriptionFragment descriptionFragment;
private ListFragment chapterFragment;
private CoverFragment coverFragment;
private ItemDescriptionFragment descriptionFragment;
private ListFragment chapterFragment;
private Fragment currentlyShownFragment;
private int currentlyShownPosition = -1;
/** Used if onResume was called without loadMediaInfo. */
private int savedPosition = -1;
private Fragment currentlyShownFragment;
private int currentlyShownPosition = -1;
/**
* Used if onResume was called without loadMediaInfo.
*/
private int savedPosition = -1;
private TextView txtvTitle;
private TextView txtvFeed;
private Button butPlaybackSpeed;
private ImageButton butNavLeft;
private ImageButton butNavRight;
private TextView txtvTitle;
private TextView txtvFeed;
private Button butPlaybackSpeed;
private ImageButton butNavLeft;
private ImageButton butNavRight;
private void resetFragmentView() {
FragmentTransaction fT = getSupportFragmentManager().beginTransaction();
private void resetFragmentView() {
FragmentTransaction fT = getSupportFragmentManager().beginTransaction();
if (coverFragment != null) {
if (AppConfig.DEBUG)
Log.d(TAG, "Removing cover fragment");
fT.remove(coverFragment);
}
if (descriptionFragment != null) {
if (AppConfig.DEBUG)
Log.d(TAG, "Removing description fragment");
fT.remove(descriptionFragment);
}
if (chapterFragment != null) {
if (AppConfig.DEBUG)
Log.d(TAG, "Removing chapter fragment");
fT.remove(chapterFragment);
}
if (currentlyShownFragment != null) {
if (AppConfig.DEBUG)
Log.d(TAG, "Removing currently shown fragment");
fT.remove(currentlyShownFragment);
}
for (int i = 0; i < detachedFragments.length; i++) {
Fragment f = detachedFragments[i];
if (f != null) {
if (AppConfig.DEBUG)
Log.d(TAG, "Removing detached fragment");
fT.remove(f);
}
}
fT.commit();
currentlyShownFragment = null;
coverFragment = null;
descriptionFragment = null;
chapterFragment = null;
currentlyShownPosition = -1;
detachedFragments = new Fragment[NUM_CONTENT_FRAGMENTS];
}
if (coverFragment != null) {
if (AppConfig.DEBUG)
Log.d(TAG, "Removing cover fragment");
fT.remove(coverFragment);
}
if (descriptionFragment != null) {
if (AppConfig.DEBUG)
Log.d(TAG, "Removing description fragment");
fT.remove(descriptionFragment);
}
if (chapterFragment != null) {
if (AppConfig.DEBUG)
Log.d(TAG, "Removing chapter fragment");
fT.remove(chapterFragment);
}
if (currentlyShownFragment != null) {
if (AppConfig.DEBUG)
Log.d(TAG, "Removing currently shown fragment");
fT.remove(currentlyShownFragment);
}
for (int i = 0; i < detachedFragments.length; i++) {
Fragment f = detachedFragments[i];
if (f != null) {
if (AppConfig.DEBUG)
Log.d(TAG, "Removing detached fragment");
fT.remove(f);
}
}
fT.commit();
currentlyShownFragment = null;
coverFragment = null;
descriptionFragment = null;
chapterFragment = null;
currentlyShownPosition = -1;
detachedFragments = new Fragment[NUM_CONTENT_FRAGMENTS];
}
@Override
protected void onStop() {
super.onStop();
if (AppConfig.DEBUG)
Log.d(TAG, "onStop");
@Override
protected void onStop() {
super.onStop();
if (AppConfig.DEBUG)
Log.d(TAG, "onStop");
}
}
@Override
protected void onCreate(Bundle savedInstanceState) {
requestWindowFeature(Window.FEATURE_INDETERMINATE_PROGRESS);
super.onCreate(savedInstanceState);
getSupportActionBar().setDisplayShowTitleEnabled(false);
detachedFragments = new Fragment[NUM_CONTENT_FRAGMENTS];
}
@Override
protected void onCreate(Bundle savedInstanceState) {
requestWindowFeature(Window.FEATURE_INDETERMINATE_PROGRESS);
super.onCreate(savedInstanceState);
getSupportActionBar().setDisplayShowTitleEnabled(false);
detachedFragments = new Fragment[NUM_CONTENT_FRAGMENTS];
}
private void savePreferences() {
if (AppConfig.DEBUG)
Log.d(TAG, "Saving preferences");
SharedPreferences prefs = getSharedPreferences(PREFS, MODE_PRIVATE);
SharedPreferences.Editor editor = prefs.edit();
if (currentlyShownPosition >= 0 && controller != null
&& controller.getMedia() != null) {
editor.putInt(PREF_KEY_SELECTED_FRAGMENT_POSITION,
currentlyShownPosition);
editor.putString(PREF_PLAYABLE_ID, controller.getMedia()
.getIdentifier().toString());
} else {
editor.putInt(PREF_KEY_SELECTED_FRAGMENT_POSITION, -1);
editor.putString(PREF_PLAYABLE_ID, "");
}
editor.commit();
private void savePreferences() {
if (AppConfig.DEBUG)
Log.d(TAG, "Saving preferences");
SharedPreferences prefs = getSharedPreferences(PREFS, MODE_PRIVATE);
SharedPreferences.Editor editor = prefs.edit();
if (currentlyShownPosition >= 0 && controller != null
&& controller.getMedia() != null) {
editor.putInt(PREF_KEY_SELECTED_FRAGMENT_POSITION,
currentlyShownPosition);
editor.putString(PREF_PLAYABLE_ID, controller.getMedia()
.getIdentifier().toString());
} else {
editor.putInt(PREF_KEY_SELECTED_FRAGMENT_POSITION, -1);
editor.putString(PREF_PLAYABLE_ID, "");
}
editor.commit();
savedPosition = currentlyShownPosition;
}
savedPosition = currentlyShownPosition;
}
@Override
public void onConfigurationChanged(Configuration newConfig) {
super.onConfigurationChanged(newConfig);
}
@Override
public void onConfigurationChanged(Configuration newConfig) {
super.onConfigurationChanged(newConfig);
}
@Override
protected void onSaveInstanceState(Bundle outState) {
// super.onSaveInstanceState(outState); would cause crash
if (AppConfig.DEBUG)
Log.d(TAG, "onSaveInstanceState");
}
@Override
protected void onSaveInstanceState(Bundle outState) {
// super.onSaveInstanceState(outState); would cause crash
if (AppConfig.DEBUG)
Log.d(TAG, "onSaveInstanceState");
}
@Override
protected void onPause() {
savePreferences();
resetFragmentView();
super.onPause();
}
@Override
protected void onPause() {
savePreferences();
resetFragmentView();
super.onPause();
}
@Override
protected void onRestoreInstanceState(Bundle savedInstanceState) {
super.onRestoreInstanceState(savedInstanceState);
restoreFromPreferences();
}
@Override
protected void onRestoreInstanceState(Bundle savedInstanceState) {
super.onRestoreInstanceState(savedInstanceState);
restoreFromPreferences();
}
/**
* Tries to restore the selected fragment position from the Activity's
* preferences.
*
* @return true if restoreFromPrefernces changed the activity's state
* */
private boolean restoreFromPreferences() {
if (AppConfig.DEBUG)
Log.d(TAG, "Restoring instance state");
SharedPreferences prefs = getSharedPreferences(PREFS, MODE_PRIVATE);
int savedPosition = prefs.getInt(PREF_KEY_SELECTED_FRAGMENT_POSITION,
-1);
String playableId = prefs.getString(PREF_PLAYABLE_ID, "");
/**
* Tries to restore the selected fragment position from the Activity's
* preferences.
*
* @return true if restoreFromPrefernces changed the activity's state
*/
private boolean restoreFromPreferences() {
if (AppConfig.DEBUG)
Log.d(TAG, "Restoring instance state");
SharedPreferences prefs = getSharedPreferences(PREFS, MODE_PRIVATE);
int savedPosition = prefs.getInt(PREF_KEY_SELECTED_FRAGMENT_POSITION,
-1);
String playableId = prefs.getString(PREF_PLAYABLE_ID, "");
if (savedPosition != -1
&& controller != null
&& controller.getMedia() != null
&& controller.getMedia().getIdentifier().toString()
.equals(playableId)) {
switchToFragment(savedPosition);
return true;
} else if (controller == null || controller.getMedia() == null) {
if (AppConfig.DEBUG)
Log.d(TAG,
"Couldn't restore from preferences: controller or media was null");
} else {
if (AppConfig.DEBUG)
Log.d(TAG,
"Couldn't restore from preferences: savedPosition was -1 or saved identifier and playable identifier didn't match.\nsavedPosition: "
+ savedPosition + ", id: " + playableId);
if (savedPosition != -1
&& controller != null
&& controller.getMedia() != null
&& controller.getMedia().getIdentifier().toString()
.equals(playableId)) {
switchToFragment(savedPosition);
return true;
} else if (controller == null || controller.getMedia() == null) {
if (AppConfig.DEBUG)
Log.d(TAG,
"Couldn't restore from preferences: controller or media was null");
} else {
if (AppConfig.DEBUG)
Log.d(TAG,
"Couldn't restore from preferences: savedPosition was -1 or saved identifier and playable identifier didn't match.\nsavedPosition: "
+ savedPosition + ", id: " + playableId);
}
return false;
}
}
return false;
}
@Override
protected void onResume() {
super.onResume();
if (getIntent().getAction() != null
&& getIntent().getAction().equals(Intent.ACTION_VIEW)) {
Intent intent = getIntent();
if (AppConfig.DEBUG)
Log.d(TAG, "Received VIEW intent: "
+ intent.getData().getPath());
ExternalMedia media = new ExternalMedia(intent.getData().getPath(),
MediaType.AUDIO);
Intent launchIntent = new Intent(this, PlaybackService.class);
launchIntent.putExtra(PlaybackService.EXTRA_PLAYABLE, media);
launchIntent.putExtra(PlaybackService.EXTRA_START_WHEN_PREPARED,
true);
launchIntent.putExtra(PlaybackService.EXTRA_SHOULD_STREAM, false);
launchIntent.putExtra(PlaybackService.EXTRA_PREPARE_IMMEDIATELY,
true);
startService(launchIntent);
}
if (savedPosition != -1) {
switchToFragment(savedPosition);
}
@Override
protected void onResume() {
super.onResume();
if (getIntent().getAction() != null
&& getIntent().getAction().equals(Intent.ACTION_VIEW)) {
Intent intent = getIntent();
if (AppConfig.DEBUG)
Log.d(TAG, "Received VIEW intent: "
+ intent.getData().getPath());
ExternalMedia media = new ExternalMedia(intent.getData().getPath(),
MediaType.AUDIO);
Intent launchIntent = new Intent(this, PlaybackService.class);
launchIntent.putExtra(PlaybackService.EXTRA_PLAYABLE, media);
launchIntent.putExtra(PlaybackService.EXTRA_START_WHEN_PREPARED,
true);
launchIntent.putExtra(PlaybackService.EXTRA_SHOULD_STREAM, false);
launchIntent.putExtra(PlaybackService.EXTRA_PREPARE_IMMEDIATELY,
true);
startService(launchIntent);
}
if (savedPosition != -1) {
switchToFragment(savedPosition);
}
}
}
@Override
protected void onNewIntent(Intent intent) {
super.onNewIntent(intent);
setIntent(intent);
}
@Override
protected void onNewIntent(Intent intent) {
super.onNewIntent(intent);
setIntent(intent);
}
@Override
protected void onAwaitingVideoSurface() {
startActivity(new Intent(this, VideoplayerActivity.class));
}
@Override
protected void onAwaitingVideoSurface() {
if (AppConfig.DEBUG) Log.d(TAG, "onAwaitingVideoSurface was called in audio player -> switching to video player");
startActivity(new Intent(this, VideoplayerActivity.class));
}
@Override
protected void postStatusMsg(int resId) {
setSupportProgressBarIndeterminateVisibility(resId == R.string.player_preparing_msg
|| resId == R.string.player_seeking_msg
|| resId == R.string.player_buffering_msg);
}
@Override
protected void postStatusMsg(int resId) {
setSupportProgressBarIndeterminateVisibility(resId == R.string.player_preparing_msg
|| resId == R.string.player_seeking_msg
|| resId == R.string.player_buffering_msg);
}
@Override
protected void clearStatusMsg() {
setSupportProgressBarIndeterminateVisibility(false);
}
@Override
protected void clearStatusMsg() {
setSupportProgressBarIndeterminateVisibility(false);
}
/**
* Changes the currently displayed fragment.
*
* @param pos Must be POS_COVER, POS_DESCR, or POS_CHAPTERS
* */
private void switchToFragment(int pos) {
if (AppConfig.DEBUG)
Log.d(TAG, "Switching contentView to position " + pos);
if (currentlyShownPosition != pos && controller != null) {
Playable media = controller.getMedia();
if (media != null) {
FragmentTransaction ft = getSupportFragmentManager()
.beginTransaction();
if (currentlyShownFragment != null) {
detachedFragments[currentlyShownPosition] = currentlyShownFragment;
ft.detach(currentlyShownFragment);
}
switch (pos) {
case POS_COVER:
if (coverFragment == null) {
Log.i(TAG, "Using new coverfragment");
coverFragment = CoverFragment.newInstance(media);
}
currentlyShownFragment = coverFragment;
break;
case POS_DESCR:
if (descriptionFragment == null) {
descriptionFragment = ItemDescriptionFragment
.newInstance(media, true);
}
currentlyShownFragment = descriptionFragment;
break;
case POS_CHAPTERS:
if (chapterFragment == null) {
chapterFragment = new ListFragment() {
/**
* Changes the currently displayed fragment.
*
* @param pos Must be POS_COVER, POS_DESCR, or POS_CHAPTERS
*/
private void switchToFragment(int pos) {
if (AppConfig.DEBUG)
Log.d(TAG, "Switching contentView to position " + pos);
if (currentlyShownPosition != pos && controller != null) {
Playable media = controller.getMedia();
if (media != null) {
FragmentTransaction ft = getSupportFragmentManager()
.beginTransaction();
if (currentlyShownFragment != null) {
detachedFragments[currentlyShownPosition] = currentlyShownFragment;
ft.detach(currentlyShownFragment);
}
switch (pos) {
case POS_COVER:
if (coverFragment == null) {
Log.i(TAG, "Using new coverfragment");
coverFragment = CoverFragment.newInstance(media);
}
currentlyShownFragment = coverFragment;
break;
case POS_DESCR:
if (descriptionFragment == null) {
descriptionFragment = ItemDescriptionFragment
.newInstance(media, true);
}
currentlyShownFragment = descriptionFragment;
break;
case POS_CHAPTERS:
if (chapterFragment == null) {
chapterFragment = new ListFragment() {
@Override
public void onListItemClick(ListView l, View v,
int position, long id) {
super.onListItemClick(l, v, position, id);
Chapter chapter = (Chapter) this
.getListAdapter().getItem(position);
controller.seekToChapter(chapter);
}
@Override
public void onListItemClick(ListView l, View v,
int position, long id) {
super.onListItemClick(l, v, position, id);
Chapter chapter = (Chapter) this
.getListAdapter().getItem(position);
controller.seekToChapter(chapter);
}
};
chapterFragment.setListAdapter(new ChapterListAdapter(
AudioplayerActivity.this, 0, media
.getChapters(), media));
}
currentlyShownFragment = chapterFragment;
break;
}
if (currentlyShownFragment != null) {
currentlyShownPosition = pos;
if (detachedFragments[pos] != null) {
if (AppConfig.DEBUG)
Log.d(TAG, "Reattaching fragment at position "
+ pos);
ft.attach(detachedFragments[pos]);
} else {
ft.add(R.id.contentView, currentlyShownFragment);
}
ft.setTransition(FragmentTransaction.TRANSIT_FRAGMENT_OPEN);
ft.disallowAddToBackStack();
ft.commit();
updateNavButtonDrawable();
}
}
}
}
};
chapterFragment.setListAdapter(new ChapterListAdapter(
AudioplayerActivity.this, 0, media
.getChapters(), media));
}
currentlyShownFragment = chapterFragment;
break;
}
if (currentlyShownFragment != null) {
currentlyShownPosition = pos;
if (detachedFragments[pos] != null) {
if (AppConfig.DEBUG)
Log.d(TAG, "Reattaching fragment at position "
+ pos);
ft.attach(detachedFragments[pos]);
} else {
ft.add(R.id.contentView, currentlyShownFragment);
}
ft.setTransition(FragmentTransaction.TRANSIT_FRAGMENT_OPEN);
ft.disallowAddToBackStack();
ft.commit();
updateNavButtonDrawable();
}
}
}
}
private void updateNavButtonDrawable() {
TypedArray drawables = obtainStyledAttributes(new int[] {
R.attr.navigation_shownotes, R.attr.navigation_chapters });
final Playable media = controller.getMedia();
if (butNavLeft != null && butNavRight != null && media != null) {
switch (currentlyShownPosition) {
case POS_COVER:
butNavLeft.setScaleType(ScaleType.CENTER);
butNavLeft.setImageDrawable(drawables.getDrawable(0));
butNavRight.setImageDrawable(drawables.getDrawable(1));
break;
case POS_DESCR:
butNavLeft.setScaleType(ScaleType.CENTER_CROP);
butNavLeft.post(new Runnable() {
private void updateNavButtonDrawable() {
@Override
public void run() {
ImageLoader.getInstance().loadThumbnailBitmap(media,
butNavLeft);
}
});
butNavRight.setImageDrawable(drawables.getDrawable(1));
break;
case POS_CHAPTERS:
butNavLeft.setScaleType(ScaleType.CENTER_CROP);
butNavLeft.post(new Runnable() {
final int[] buttonTexts = new int[] {R.string.show_shownotes_label,
R.string.show_chapters_label, R.string.show_cover_label};
@Override
public void run() {
ImageLoader.getInstance().loadThumbnailBitmap(media,
butNavLeft);
}
});
butNavRight.setImageDrawable(drawables.getDrawable(0));
break;
}
}
}
final TypedArray drawables = obtainStyledAttributes(new int[]{
R.attr.navigation_shownotes, R.attr.navigation_chapters});
final Playable media = controller.getMedia();
if (butNavLeft != null && butNavRight != null && media != null) {
switch (currentlyShownPosition) {
case POS_COVER:
butNavLeft.setScaleType(ScaleType.CENTER);
butNavLeft.setImageDrawable(drawables.getDrawable(0));
butNavLeft.setContentDescription(getString(buttonTexts[0]));
@Override
protected void setupGUI() {
super.setupGUI();
resetFragmentView();
txtvTitle = (TextView) findViewById(R.id.txtvTitle);
txtvFeed = (TextView) findViewById(R.id.txtvFeed);
butNavLeft = (ImageButton) findViewById(R.id.butNavLeft);
butNavRight = (ImageButton) findViewById(R.id.butNavRight);
butPlaybackSpeed = (Button) findViewById(R.id.butPlaybackSpeed);
butNavRight.setImageDrawable(drawables.getDrawable(1));
butNavRight.setContentDescription(getString(buttonTexts[1]));
butNavLeft.setOnClickListener(new OnClickListener() {
break;
case POS_DESCR:
butNavLeft.setScaleType(ScaleType.CENTER_CROP);
butNavLeft.post(new Runnable() {
@Override
public void onClick(View v) {
if (currentlyShownFragment == null
|| currentlyShownPosition == POS_DESCR) {
switchToFragment(POS_COVER);
} else if (currentlyShownPosition == POS_COVER) {
switchToFragment(POS_DESCR);
} else if (currentlyShownPosition == POS_CHAPTERS) {
switchToFragment(POS_COVER);
}
}
});
@Override
public void run() {
ImageLoader.getInstance().loadThumbnailBitmap(media,
butNavLeft);
}
});
butNavLeft.setContentDescription(getString(buttonTexts[2]));
butNavRight.setOnClickListener(new OnClickListener() {
butNavRight.setImageDrawable(drawables.getDrawable(1));
butNavRight.setContentDescription(getString(buttonTexts[1]));
break;
case POS_CHAPTERS:
butNavLeft.setScaleType(ScaleType.CENTER_CROP);
butNavLeft.post(new Runnable() {
@Override
public void onClick(View v) {
if (currentlyShownPosition == POS_CHAPTERS) {
switchToFragment(POS_DESCR);
} else {
switchToFragment(POS_CHAPTERS);
}
}
});
@Override
public void run() {
ImageLoader.getInstance().loadThumbnailBitmap(media,
butNavLeft);
}
});
butNavLeft.setContentDescription(getString(buttonTexts[2]));
butPlaybackSpeed.setOnClickListener(new OnClickListener() {
butNavRight.setImageDrawable(drawables.getDrawable(0));
butNavRight.setContentDescription(getString(buttonTexts[0]));
break;
}
}
}
@Override
protected void setupGUI() {
super.setupGUI();
resetFragmentView();
txtvTitle = (TextView) findViewById(R.id.txtvTitle);
txtvFeed = (TextView) findViewById(R.id.txtvFeed);
butNavLeft = (ImageButton) findViewById(R.id.butNavLeft);
butNavRight = (ImageButton) findViewById(R.id.butNavRight);
butPlaybackSpeed = (Button) findViewById(R.id.butPlaybackSpeed);
butNavLeft.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
if (controller != null && controller.canSetPlaybackSpeed()) {
String[] availableSpeeds = UserPreferences
.getPlaybackSpeedArray();
String currentSpeed = UserPreferences.getPlaybackSpeed();
// Provide initial value in case the speed list has changed
// out from under us
// and our current speed isn't in the new list
String newSpeed;
if (availableSpeeds.length > 0) {
newSpeed = availableSpeeds[0];
} else {
newSpeed = "1.0";
}
for (int i = 0; i < availableSpeeds.length; i++) {
if (availableSpeeds[i].equals(currentSpeed)) {
if (i == availableSpeeds.length - 1) {
newSpeed = availableSpeeds[0];
} else {
newSpeed = availableSpeeds[i + 1];
}
break;
}
}
UserPreferences.setPlaybackSpeed(newSpeed);
controller.setPlaybackSpeed(Float.parseFloat(newSpeed));
if (currentlyShownFragment == null
|| currentlyShownPosition == POS_DESCR) {
switchToFragment(POS_COVER);
} else if (currentlyShownPosition == POS_COVER) {
switchToFragment(POS_DESCR);
} else if (currentlyShownPosition == POS_CHAPTERS) {
switchToFragment(POS_COVER);
}
}
});
butPlaybackSpeed.setOnLongClickListener(new OnLongClickListener() {
butNavRight.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
if (currentlyShownPosition == POS_CHAPTERS) {
switchToFragment(POS_DESCR);
} else {
switchToFragment(POS_CHAPTERS);
}
}
});
butPlaybackSpeed.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
if (controller != null && controller.canSetPlaybackSpeed()) {
String[] availableSpeeds = UserPreferences
.getPlaybackSpeedArray();
String currentSpeed = UserPreferences.getPlaybackSpeed();
// Provide initial value in case the speed list has changed
// out from under us
// and our current speed isn't in the new list
String newSpeed;
if (availableSpeeds.length > 0) {
newSpeed = availableSpeeds[0];
} else {
newSpeed = "1.0";
}
for (int i = 0; i < availableSpeeds.length; i++) {
if (availableSpeeds[i].equals(currentSpeed)) {
if (i == availableSpeeds.length - 1) {
newSpeed = availableSpeeds[0];
} else {
newSpeed = availableSpeeds[i + 1];
}
break;
}
}
UserPreferences.setPlaybackSpeed(newSpeed);
controller.setPlaybackSpeed(Float.parseFloat(newSpeed));
}
}
});
butPlaybackSpeed.setOnLongClickListener(new OnLongClickListener() {
@Override
public boolean onLongClick(View v) {
VariableSpeedDialog.showDialog(AudioplayerActivity.this);
return true;
}
});
}
}
@Override
protected void onPlaybackSpeedChange() {
super.onPlaybackSpeedChange();
updateButPlaybackSpeed();
}
@Override
protected void onPlaybackSpeedChange() {
super.onPlaybackSpeedChange();
updateButPlaybackSpeed();
}
private void updateButPlaybackSpeed() {
if (controller == null
|| (controller.getCurrentPlaybackSpeedMultiplier() == -1)) {
butPlaybackSpeed.setVisibility(View.GONE);
} else {
butPlaybackSpeed.setVisibility(View.VISIBLE);
butPlaybackSpeed.setText(UserPreferences.getPlaybackSpeed());
}
}
private void updateButPlaybackSpeed() {
if (controller == null
|| (controller.getCurrentPlaybackSpeedMultiplier() == -1)) {
butPlaybackSpeed.setVisibility(View.GONE);
} else {
butPlaybackSpeed.setVisibility(View.VISIBLE);
butPlaybackSpeed.setText(UserPreferences.getPlaybackSpeed());
}
}
@Override
protected void onPositionObserverUpdate() {
super.onPositionObserverUpdate();
notifyMediaPositionChanged();
}
@Override
protected void onPositionObserverUpdate() {
super.onPositionObserverUpdate();
notifyMediaPositionChanged();
}
@Override
protected void loadMediaInfo() {
super.loadMediaInfo();
final Playable media = controller.getMedia();
if (media != null) {
txtvTitle.setText(media.getEpisodeTitle());
txtvFeed.setText(media.getFeedTitle());
if (media.getChapters() != null) {
butNavRight.setVisibility(View.VISIBLE);
} else {
butNavRight.setVisibility(View.GONE);
}
@Override
protected boolean loadMediaInfo() {
if (!super.loadMediaInfo()) {
return false;
}
final Playable media = controller.getMedia();
if (media == null) {
return false;
}
txtvTitle.setText(media.getEpisodeTitle());
txtvFeed.setText(media.getFeedTitle());
if (media.getChapters() != null) {
butNavRight.setVisibility(View.VISIBLE);
} else {
butNavRight.setVisibility(View.GONE);
}
}
if (currentlyShownPosition == -1) {
if (!restoreFromPreferences()) {
switchToFragment(POS_COVER);
}
}
if (currentlyShownFragment instanceof AudioplayerContentFragment) {
((AudioplayerContentFragment) currentlyShownFragment)
.onDataSetChanged(media);
}
updateButPlaybackSpeed();
}
public void notifyMediaPositionChanged() {
if (chapterFragment != null) {
ArrayAdapter<SimpleChapter> adapter = (ArrayAdapter<SimpleChapter>) chapterFragment
.getListAdapter();
adapter.notifyDataSetChanged();
}
}
if (currentlyShownPosition == -1) {
if (!restoreFromPreferences()) {
switchToFragment(POS_COVER);
}
}
if (currentlyShownFragment instanceof AudioplayerContentFragment) {
((AudioplayerContentFragment) currentlyShownFragment)
.onDataSetChanged(media);
}
updateButPlaybackSpeed();
return true;
}
@Override
protected void onReloadNotification(int notificationCode) {
if (notificationCode == PlaybackService.EXTRA_CODE_VIDEO) {
if (AppConfig.DEBUG)
Log.d(TAG,
"ReloadNotification received, switching to Videoplayer now");
startActivity(new Intent(this, VideoplayerActivity.class));
public void notifyMediaPositionChanged() {
if (chapterFragment != null) {
ArrayAdapter<SimpleChapter> adapter = (ArrayAdapter<SimpleChapter>) chapterFragment
.getListAdapter();
adapter.notifyDataSetChanged();
}
}
}
}
@Override
protected void onReloadNotification(int notificationCode) {
if (notificationCode == PlaybackService.EXTRA_CODE_VIDEO) {
if (AppConfig.DEBUG)
Log.d(TAG,
"ReloadNotification received, switching to Videoplayer now");
finish();
startActivity(new Intent(this, VideoplayerActivity.class));
@Override
protected void onBufferStart() {
postStatusMsg(R.string.player_buffering_msg);
}
}
}
@Override
protected void onBufferEnd() {
clearStatusMsg();
}
@Override
protected void onBufferStart() {
postStatusMsg(R.string.player_buffering_msg);
}
public interface AudioplayerContentFragment {
public void onDataSetChanged(Playable media);
}
@Override
protected void onBufferEnd() {
clearStatusMsg();
}
@Override
protected int getContentViewResourceId() {
return R.layout.audioplayer_activity;
}
public interface AudioplayerContentFragment {
public void onDataSetChanged(Playable media);
}
@Override
protected int getContentViewResourceId() {
return R.layout.audioplayer_activity;
}
}

View File

@ -1,18 +1,9 @@
package de.danoeh.antennapod.activity;
import android.annotation.SuppressLint;
import android.content.BroadcastReceiver;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.content.ServiceConnection;
import android.content.res.TypedArray;
import android.os.AsyncTask;
import android.os.Build;
import android.os.Bundle;
import android.os.IBinder;
import android.support.v4.app.NavUtils;
import android.os.Handler;
import android.support.v4.view.MenuItemCompat;
import android.support.v7.app.ActionBarActivity;
import android.support.v7.view.ActionMode;
@ -22,17 +13,18 @@ import android.view.MenuItem;
import android.view.View;
import android.widget.AdapterView;
import android.widget.AdapterView.OnItemLongClickListener;
import android.widget.ListView;
import de.danoeh.antennapod.AppConfig;
import de.danoeh.antennapod.R;
import de.danoeh.antennapod.adapter.DownloadlistAdapter;
import de.danoeh.antennapod.asynctask.DownloadObserver;
import de.danoeh.antennapod.preferences.UserPreferences;
import de.danoeh.antennapod.service.download.DownloadRequest;
import de.danoeh.antennapod.service.download.DownloadService;
import de.danoeh.antennapod.service.download.Downloader;
import de.danoeh.antennapod.storage.DownloadRequester;
import java.util.List;
/**
* Shows all running downloads in a list. The list objects are DownloadStatus
* objects created by a DownloadObserver.
@ -49,13 +41,10 @@ public class DownloadActivity extends ActionBarActivity implements
private ActionMode mActionMode;
private DownloadRequest selectedDownload;
private DownloadService downloadService = null;
boolean mIsBound;
private AsyncTask<Void, Void, Void> contentRefresher;
private ListView listview;
private DownloadObserver downloadObserver;
@Override
protected void onCreate(Bundle savedInstanceState) {
setTheme(UserPreferences.getTheme());
@ -68,22 +57,19 @@ public class DownloadActivity extends ActionBarActivity implements
Log.d(TAG, "Creating Activity");
requester = DownloadRequester.getInstance();
getSupportActionBar().setDisplayHomeAsUpEnabled(true);
downloadObserver = new DownloadObserver(this, new Handler(), observerCallback);
}
@Override
protected void onPause() {
super.onPause();
unbindService(mConnection);
unregisterReceiver(contentChanged);
downloadObserver.onPause();
}
@Override
protected void onResume() {
super.onResume();
registerReceiver(contentChanged, new IntentFilter(
DownloadService.ACTION_DOWNLOADS_CONTENT_CHANGED));
bindService(new Intent(this, DownloadService.class), mConnection, 0);
startContentRefresher();
downloadObserver.onResume();
if (dla != null) {
dla.notifyDataSetChanged();
}
@ -94,72 +80,8 @@ public class DownloadActivity extends ActionBarActivity implements
super.onStop();
if (AppConfig.DEBUG)
Log.d(TAG, "Stopping Activity");
stopContentRefresher();
}
private ServiceConnection mConnection = new ServiceConnection() {
public void onServiceDisconnected(ComponentName className) {
downloadService = null;
mIsBound = false;
Log.i(TAG, "Closed connection with DownloadService.");
}
public void onServiceConnected(ComponentName name, IBinder service) {
downloadService = ((DownloadService.LocalBinder) service)
.getService();
mIsBound = true;
if (AppConfig.DEBUG)
Log.d(TAG, "Connection to service established");
dla = new DownloadlistAdapter(DownloadActivity.this, 0,
downloadService.getDownloads());
listview.setAdapter(dla);
dla.notifyDataSetChanged();
}
};
@SuppressLint("NewApi")
private void startContentRefresher() {
if (contentRefresher != null) {
contentRefresher.cancel(true);
}
contentRefresher = new AsyncTask<Void, Void, Void>() {
private static final int WAITING_INTERVAL = 1000;
@Override
protected void onProgressUpdate(Void... values) {
super.onProgressUpdate(values);
if (dla != null) {
if (AppConfig.DEBUG)
Log.d(TAG, "Refreshing content automatically");
dla.notifyDataSetChanged();
}
}
@Override
protected Void doInBackground(Void... params) {
while (!isCancelled()) {
try {
Thread.sleep(WAITING_INTERVAL);
publishProgress();
} catch (InterruptedException e) {
return null;
}
}
return null;
}
};
if (android.os.Build.VERSION.SDK_INT > android.os.Build.VERSION_CODES.GINGERBREAD_MR1) {
contentRefresher.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
} else {
contentRefresher.execute();
}
}
private void stopContentRefresher() {
if (contentRefresher != null) {
contentRefresher.cancel(true);
}
}
@Override
protected void onPostCreate(Bundle savedInstanceState) {
@ -240,31 +162,29 @@ public class DownloadActivity extends ActionBarActivity implements
return handled;
}
private boolean actionModeDestroyWorkaround = false; // TODO remove this workaround
private boolean skipWorkAround = Build.VERSION.SDK_INT < Build.VERSION_CODES.ICE_CREAM_SANDWICH;
@Override
public void onDestroyActionMode(ActionMode mode) {
if (skipWorkAround || actionModeDestroyWorkaround) {
mActionMode = null;
selectedDownload = null;
dla.setSelectedItemIndex(DownloadlistAdapter.SELECTION_NONE);
actionModeDestroyWorkaround = false;
} else {
actionModeDestroyWorkaround = true;
}
mActionMode = null;
selectedDownload = null;
dla.setSelectedItemIndex(DownloadlistAdapter.SELECTION_NONE);
}
private BroadcastReceiver contentChanged = new BroadcastReceiver() {
private DownloadObserver.Callback observerCallback = new DownloadObserver.Callback() {
@Override
public void onReceive(Context context, Intent intent) {
public void onContentChanged() {
if (dla != null) {
if (AppConfig.DEBUG)
Log.d(TAG, "Refreshing content");
dla.notifyDataSetChanged();
}
}
@Override
public void onDownloadDataAvailable(List<Downloader> downloaderList) {
dla = new DownloadlistAdapter(DownloadActivity.this, 0,
downloaderList);
listview.setAdapter(dla);
dla.notifyDataSetChanged();
}
};
}

View File

@ -29,7 +29,7 @@ import de.danoeh.antennapod.fragment.EpisodesFragment;
import de.danoeh.antennapod.fragment.ExternalPlayerFragment;
import de.danoeh.antennapod.fragment.FeedlistFragment;
import de.danoeh.antennapod.preferences.UserPreferences;
import de.danoeh.antennapod.service.PlaybackService;
import de.danoeh.antennapod.service.playback.PlaybackService;
import de.danoeh.antennapod.service.download.DownloadService;
import de.danoeh.antennapod.storage.DBReader;
import de.danoeh.antennapod.storage.DBTasks;

View File

@ -16,13 +16,14 @@ import android.widget.ImageButton;
import android.widget.SeekBar;
import android.widget.SeekBar.OnSeekBarChangeListener;
import android.widget.TextView;
import de.danoeh.antennapod.AppConfig;
import de.danoeh.antennapod.R;
import de.danoeh.antennapod.asynctask.FlattrClickWorker;
import de.danoeh.antennapod.dialog.TimeDialog;
import de.danoeh.antennapod.feed.FeedItem;
import de.danoeh.antennapod.feed.FeedMedia;
import de.danoeh.antennapod.preferences.UserPreferences;
import de.danoeh.antennapod.service.PlaybackService;
import de.danoeh.antennapod.service.playback.PlaybackService;
import de.danoeh.antennapod.storage.DBTasks;
import de.danoeh.antennapod.util.Converter;
import de.danoeh.antennapod.util.ShareUtils;
import de.danoeh.antennapod.util.StorageUtils;
@ -35,427 +36,442 @@ import de.danoeh.antennapod.util.playback.PlaybackController;
* files.
*/
public abstract class MediaplayerActivity extends ActionBarActivity
implements OnSeekBarChangeListener {
private static final String TAG = "MediaplayerActivity";
implements OnSeekBarChangeListener {
private static final String TAG = "MediaplayerActivity";
protected PlaybackController controller;
protected PlaybackController controller;
protected TextView txtvPosition;
protected TextView txtvLength;
protected SeekBar sbPosition;
protected ImageButton butPlay;
protected ImageButton butRev;
protected ImageButton butFF;
protected TextView txtvPosition;
protected TextView txtvLength;
protected SeekBar sbPosition;
protected ImageButton butPlay;
protected ImageButton butRev;
protected ImageButton butFF;
private PlaybackController newPlaybackController() {
return new PlaybackController(this, false) {
private PlaybackController newPlaybackController() {
return new PlaybackController(this, false) {
@Override
public void setupGUI() {
MediaplayerActivity.this.setupGUI();
}
@Override
public void setupGUI() {
MediaplayerActivity.this.setupGUI();
}
@Override
public void onPositionObserverUpdate() {
MediaplayerActivity.this.onPositionObserverUpdate();
}
@Override
public void onPositionObserverUpdate() {
MediaplayerActivity.this.onPositionObserverUpdate();
}
@Override
public void onBufferStart() {
MediaplayerActivity.this.onBufferStart();
}
@Override
public void onBufferStart() {
MediaplayerActivity.this.onBufferStart();
}
@Override
public void onBufferEnd() {
MediaplayerActivity.this.onBufferEnd();
}
@Override
public void onBufferEnd() {
MediaplayerActivity.this.onBufferEnd();
}
@Override
public void onBufferUpdate(float progress) {
MediaplayerActivity.this.onBufferUpdate(progress);
}
@Override
public void onBufferUpdate(float progress) {
MediaplayerActivity.this.onBufferUpdate(progress);
}
@Override
public void handleError(int code) {
MediaplayerActivity.this.handleError(code);
}
@Override
public void handleError(int code) {
MediaplayerActivity.this.handleError(code);
}
@Override
public void onReloadNotification(int code) {
MediaplayerActivity.this.onReloadNotification(code);
}
@Override
public void onReloadNotification(int code) {
MediaplayerActivity.this.onReloadNotification(code);
}
@Override
public void onSleepTimerUpdate() {
supportInvalidateOptionsMenu();
}
@Override
public void onSleepTimerUpdate() {
supportInvalidateOptionsMenu();
}
@Override
public ImageButton getPlayButton() {
return butPlay;
}
@Override
public ImageButton getPlayButton() {
return butPlay;
}
@Override
public void postStatusMsg(int msg) {
MediaplayerActivity.this.postStatusMsg(msg);
}
@Override
public void postStatusMsg(int msg) {
MediaplayerActivity.this.postStatusMsg(msg);
}
@Override
public void clearStatusMsg() {
MediaplayerActivity.this.clearStatusMsg();
}
@Override
public void clearStatusMsg() {
MediaplayerActivity.this.clearStatusMsg();
}
@Override
public void loadMediaInfo() {
MediaplayerActivity.this.loadMediaInfo();
}
@Override
public boolean loadMediaInfo() {
return MediaplayerActivity.this.loadMediaInfo();
}
@Override
public void onAwaitingVideoSurface() {
MediaplayerActivity.this.onAwaitingVideoSurface();
}
@Override
public void onAwaitingVideoSurface() {
MediaplayerActivity.this.onAwaitingVideoSurface();
}
@Override
public void onServiceQueried() {
MediaplayerActivity.this.onServiceQueried();
}
@Override
public void onServiceQueried() {
MediaplayerActivity.this.onServiceQueried();
}
@Override
public void onShutdownNotification() {
finish();
}
@Override
public void onShutdownNotification() {
finish();
}
@Override
public void onPlaybackEnd() {
finish();
}
@Override
public void onPlaybackEnd() {
finish();
}
@Override
public void onPlaybackSpeedChange() {
MediaplayerActivity.this.onPlaybackSpeedChange();
}
};
@Override
public void onPlaybackSpeedChange() {
MediaplayerActivity.this.onPlaybackSpeedChange();
}
};
}
}
protected void onPlaybackSpeedChange() {
protected void onPlaybackSpeedChange() {
}
}
protected void onServiceQueried() {
supportInvalidateOptionsMenu();
}
protected void onServiceQueried() {
supportInvalidateOptionsMenu();
}
@Override
protected void onCreate(Bundle savedInstanceState) {
setTheme(UserPreferences.getTheme());
super.onCreate(savedInstanceState);
if (AppConfig.DEBUG)
Log.d(TAG, "Creating Activity");
StorageUtils.checkStorageAvailability(this);
protected void chooseTheme() {
setTheme(UserPreferences.getTheme());
}
@Override
protected void onCreate(Bundle savedInstanceState) {
chooseTheme();
super.onCreate(savedInstanceState);
if (AppConfig.DEBUG)
Log.d(TAG, "Creating Activity");
StorageUtils.checkStorageAvailability(this);
setVolumeControlStream(AudioManager.STREAM_MUSIC);
orientation = getResources().getConfiguration().orientation;
getWindow().setFormat(PixelFormat.TRANSPARENT);
getSupportActionBar().setDisplayHomeAsUpEnabled(true);
}
getWindow().setFormat(PixelFormat.TRANSPARENT);
getSupportActionBar().setDisplayHomeAsUpEnabled(true);
}
@Override
protected void onPause() {
super.onPause();
controller.reinitServiceIfPaused();
controller.pause();
}
@Override
protected void onPause() {
super.onPause();
controller.reinitServiceIfPaused();
controller.pause();
}
/**
* Should be used to switch to another player activity if the mime type is
* not the correct one for the current activity.
*/
protected abstract void onReloadNotification(int notificationCode);
/**
* Should be used to switch to another player activity if the mime type is
* not the correct one for the current activity.
*/
protected abstract void onReloadNotification(int notificationCode);
/**
* Should be used to inform the user that the PlaybackService is currently
* buffering.
*/
protected abstract void onBufferStart();
/**
* Should be used to inform the user that the PlaybackService is currently
* buffering.
*/
protected abstract void onBufferStart();
/**
* Should be used to hide the view that was showing the 'buffering'-message.
*/
protected abstract void onBufferEnd();
/**
* Should be used to hide the view that was showing the 'buffering'-message.
*/
protected abstract void onBufferEnd();
protected void onBufferUpdate(float progress) {
if (sbPosition != null) {
sbPosition.setSecondaryProgress((int) progress
* sbPosition.getMax());
}
}
protected void onBufferUpdate(float progress) {
if (sbPosition != null) {
sbPosition.setSecondaryProgress((int) progress
* sbPosition.getMax());
}
}
/** Current screen orientation. */
protected int orientation;
/**
* Current screen orientation.
*/
protected int orientation;
@Override
protected void onStart() {
super.onStart();
if (controller != null) {
controller.release();
}
controller = newPlaybackController();
}
@Override
protected void onStart() {
super.onStart();
if (controller != null) {
controller.release();
}
controller = newPlaybackController();
}
@Override
protected void onStop() {
super.onStop();
if (AppConfig.DEBUG)
Log.d(TAG, "Activity stopped");
if (controller != null) {
controller.release();
}
}
@Override
protected void onStop() {
super.onStop();
if (AppConfig.DEBUG)
Log.d(TAG, "Activity stopped");
if (controller != null) {
controller.release();
}
}
@Override
protected void onDestroy() {
super.onDestroy();
if (AppConfig.DEBUG)
Log.d(TAG, "Activity destroyed");
}
@Override
protected void onDestroy() {
super.onDestroy();
if (AppConfig.DEBUG)
Log.d(TAG, "Activity destroyed");
}
@Override
public boolean onCreateOptionsMenu(Menu menu) {
@Override
public boolean onCreateOptionsMenu(Menu menu) {
super.onCreateOptionsMenu(menu);
MenuInflater inflater = getMenuInflater();
inflater.inflate(R.menu.mediaplayer, menu);
return true;
}
MenuInflater inflater = getMenuInflater();
inflater.inflate(R.menu.mediaplayer, menu);
return true;
}
@Override
public boolean onPrepareOptionsMenu(Menu menu) {
@Override
public boolean onPrepareOptionsMenu(Menu menu) {
super.onPrepareOptionsMenu(menu);
Playable media = controller.getMedia();
Playable media = controller.getMedia();
menu.findItem(R.id.support_item).setVisible(
media != null && media.getPaymentLink() != null);
menu.findItem(R.id.share_link_item).setVisible(
media != null && media.getWebsiteLink() != null);
menu.findItem(R.id.visit_website_item).setVisible(
media != null && media.getWebsiteLink() != null);
menu.findItem(R.id.skip_episode_item).setVisible(media != null);
boolean sleepTimerSet = controller.sleepTimerActive();
boolean sleepTimerNotSet = controller.sleepTimerNotActive();
menu.findItem(R.id.set_sleeptimer_item).setVisible(sleepTimerNotSet);
menu.findItem(R.id.disable_sleeptimer_item).setVisible(sleepTimerSet);
return true;
}
menu.findItem(R.id.support_item).setVisible(
media != null && media.getPaymentLink() != null &&
(media instanceof FeedMedia) &&
((FeedMedia) media).getItem().getFlattrStatus().flattrable());
menu.findItem(R.id.share_link_item).setVisible(
media != null && media.getWebsiteLink() != null);
menu.findItem(R.id.visit_website_item).setVisible(
media != null && media.getWebsiteLink() != null);
menu.findItem(R.id.skip_episode_item).setVisible(media != null);
boolean sleepTimerSet = controller.sleepTimerActive();
boolean sleepTimerNotSet = controller.sleepTimerNotActive();
menu.findItem(R.id.set_sleeptimer_item).setVisible(sleepTimerNotSet);
menu.findItem(R.id.disable_sleeptimer_item).setVisible(sleepTimerSet);
return true;
}
@Override
public boolean onOptionsItemSelected(MenuItem item) {
Playable media = controller.getMedia();
if (item.getItemId() == android.R.id.home) {
Intent intent = new Intent(MediaplayerActivity.this,
MainActivity.class);
intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP
| Intent.FLAG_ACTIVITY_NEW_TASK);
startActivity(intent);
return true;
} else if (media != null) {
switch (item.getItemId()) {
case R.id.disable_sleeptimer_item:
if (controller.serviceAvailable()) {
AlertDialog.Builder stDialog = new AlertDialog.Builder(this);
stDialog.setTitle(R.string.sleep_timer_label);
stDialog.setMessage(getString(R.string.time_left_label)
+ Converter.getDurationStringLong((int) controller
.getSleepTimerTimeLeft()));
stDialog.setPositiveButton(
R.string.disable_sleeptimer_label,
new DialogInterface.OnClickListener() {
@Override
public boolean onOptionsItemSelected(MenuItem item) {
Playable media = controller.getMedia();
if (item.getItemId() == android.R.id.home) {
Intent intent = new Intent(MediaplayerActivity.this,
MainActivity.class);
intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP
| Intent.FLAG_ACTIVITY_NEW_TASK);
startActivity(intent);
return true;
} else if (media != null) {
switch (item.getItemId()) {
case R.id.disable_sleeptimer_item:
if (controller.serviceAvailable()) {
AlertDialog.Builder stDialog = new AlertDialog.Builder(this);
stDialog.setTitle(R.string.sleep_timer_label);
stDialog.setMessage(getString(R.string.time_left_label)
+ Converter.getDurationStringLong((int) controller
.getSleepTimerTimeLeft()));
stDialog.setPositiveButton(
R.string.disable_sleeptimer_label,
new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog,
int which) {
dialog.dismiss();
controller.disableSleepTimer();
}
});
stDialog.setNegativeButton(R.string.cancel_label,
new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog,
int which) {
dialog.dismiss();
controller.disableSleepTimer();
}
});
stDialog.setNegativeButton(R.string.cancel_label,
new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog,
int which) {
dialog.dismiss();
}
});
stDialog.create().show();
}
break;
case R.id.set_sleeptimer_item:
if (controller.serviceAvailable()) {
TimeDialog td = new TimeDialog(this,
R.string.set_sleeptimer_label,
R.string.set_sleeptimer_label) {
@Override
public void onClick(DialogInterface dialog,
int which) {
dialog.dismiss();
}
});
stDialog.create().show();
}
break;
case R.id.set_sleeptimer_item:
if (controller.serviceAvailable()) {
TimeDialog td = new TimeDialog(this,
R.string.set_sleeptimer_label,
R.string.set_sleeptimer_label) {
@Override
public void onTimeEntered(long millis) {
controller.setSleepTimer(millis);
}
};
td.show();
break;
@Override
public void onTimeEntered(long millis) {
controller.setSleepTimer(millis);
}
};
td.show();
break;
}
case R.id.visit_website_item:
Uri uri = Uri.parse(media.getWebsiteLink());
startActivity(new Intent(Intent.ACTION_VIEW, uri));
break;
case R.id.support_item:
new FlattrClickWorker(this, media.getPaymentLink())
.executeAsync();
break;
case R.id.share_link_item:
ShareUtils.shareLink(this, media.getWebsiteLink());
break;
case R.id.skip_episode_item:
sendBroadcast(new Intent(
PlaybackService.ACTION_SKIP_CURRENT_EPISODE));
break;
default:
return false;
}
case R.id.visit_website_item:
Uri uri = Uri.parse(media.getWebsiteLink());
startActivity(new Intent(Intent.ACTION_VIEW, uri));
break;
case R.id.support_item:
if (media instanceof FeedMedia) {
FeedItem feedItem = ((FeedMedia) media).getItem();
DBTasks.flattrItemIfLoggedIn(this, feedItem);
}
break;
case R.id.share_link_item:
ShareUtils.shareLink(this, media.getWebsiteLink());
break;
case R.id.skip_episode_item:
sendBroadcast(new Intent(
PlaybackService.ACTION_SKIP_CURRENT_EPISODE));
break;
default:
return false;
}
return true;
} else {
return false;
}
}
}
return true;
} else {
return false;
}
}
@Override
protected void onResume() {
super.onResume();
if (AppConfig.DEBUG)
Log.d(TAG, "Resuming Activity");
StorageUtils.checkStorageAvailability(this);
controller.init();
}
@Override
protected void onResume() {
super.onResume();
if (AppConfig.DEBUG)
Log.d(TAG, "Resuming Activity");
StorageUtils.checkStorageAvailability(this);
controller.init();
}
/**
* Called by 'handleStatus()' when the PlaybackService is in the
* AWAITING_VIDEO_SURFACE state.
*/
protected abstract void onAwaitingVideoSurface();
/**
* Called by 'handleStatus()' when the PlaybackService is waiting for
* a video surface.
*/
protected abstract void onAwaitingVideoSurface();
protected abstract void postStatusMsg(int resId);
protected abstract void postStatusMsg(int resId);
protected abstract void clearStatusMsg();
protected abstract void clearStatusMsg();
protected void onPositionObserverUpdate() {
if (controller != null) {
int currentPosition = controller.getPosition();
int duration = controller.getDuration();
if (currentPosition != PlaybackService.INVALID_TIME
&& duration != PlaybackService.INVALID_TIME
&& controller.getMedia() != null) {
controller.getMedia().setPosition(currentPosition);
txtvPosition.setText(Converter
.getDurationStringLong(currentPosition));
txtvLength.setText(Converter.getDurationStringLong(duration));
updateProgressbarPosition(currentPosition, duration);
} else {
Log.w(TAG,
"Could not react to position observer update because of invalid time");
}
}
}
protected void onPositionObserverUpdate() {
if (controller != null) {
int currentPosition = controller.getPosition();
int duration = controller.getDuration();
if (currentPosition != PlaybackService.INVALID_TIME
&& duration != PlaybackService.INVALID_TIME
&& controller.getMedia() != null) {
txtvPosition.setText(Converter
.getDurationStringLong(currentPosition));
txtvLength.setText(Converter.getDurationStringLong(duration));
updateProgressbarPosition(currentPosition, duration);
} else {
Log.w(TAG,
"Could not react to position observer update because of invalid time");
}
}
}
private void updateProgressbarPosition(int position, int duration) {
if (AppConfig.DEBUG)
Log.d(TAG, "Updating progressbar info");
float progress = ((float) position) / duration;
sbPosition.setProgress((int) (progress * sbPosition.getMax()));
}
private void updateProgressbarPosition(int position, int duration) {
if (AppConfig.DEBUG)
Log.d(TAG, "Updating progressbar info");
float progress = ((float) position) / duration;
sbPosition.setProgress((int) (progress * sbPosition.getMax()));
}
/**
* Load information about the media that is going to be played or currently
* being played. This method will be called when the activity is connected
* to the PlaybackService to ensure that the activity has the right
* FeedMedia object.
*/
protected void loadMediaInfo() {
if (AppConfig.DEBUG)
Log.d(TAG, "Loading media info");
Playable media = controller.getMedia();
if (media != null) {
txtvPosition.setText(Converter.getDurationStringLong((media
.getPosition())));
/**
* Load information about the media that is going to be played or currently
* being played. This method will be called when the activity is connected
* to the PlaybackService to ensure that the activity has the right
* FeedMedia object.
*/
protected boolean loadMediaInfo() {
if (AppConfig.DEBUG)
Log.d(TAG, "Loading media info");
Playable media = controller.getMedia();
if (media != null) {
txtvPosition.setText(Converter.getDurationStringLong((media
.getPosition())));
if (media.getDuration() != 0) {
txtvLength.setText(Converter.getDurationStringLong(media
.getDuration()));
float progress = ((float) media.getPosition())
/ media.getDuration();
sbPosition.setProgress((int) (progress * sbPosition.getMax()));
}
}
}
if (media.getDuration() != 0) {
txtvLength.setText(Converter.getDurationStringLong(media
.getDuration()));
float progress = ((float) media.getPosition())
/ media.getDuration();
sbPosition.setProgress((int) (progress * sbPosition.getMax()));
}
return true;
} else {
return false;
}
}
protected void setupGUI() {
setContentView(getContentViewResourceId());
sbPosition = (SeekBar) findViewById(R.id.sbPosition);
txtvPosition = (TextView) findViewById(R.id.txtvPosition);
txtvLength = (TextView) findViewById(R.id.txtvLength);
butPlay = (ImageButton) findViewById(R.id.butPlay);
butRev = (ImageButton) findViewById(R.id.butRev);
butFF = (ImageButton) findViewById(R.id.butFF);
protected void setupGUI() {
setContentView(getContentViewResourceId());
sbPosition = (SeekBar) findViewById(R.id.sbPosition);
txtvPosition = (TextView) findViewById(R.id.txtvPosition);
txtvLength = (TextView) findViewById(R.id.txtvLength);
butPlay = (ImageButton) findViewById(R.id.butPlay);
butRev = (ImageButton) findViewById(R.id.butRev);
butFF = (ImageButton) findViewById(R.id.butFF);
// SEEKBAR SETUP
// SEEKBAR SETUP
sbPosition.setOnSeekBarChangeListener(this);
sbPosition.setOnSeekBarChangeListener(this);
// BUTTON SETUP
// BUTTON SETUP
butPlay.setOnClickListener(controller.newOnPlayButtonClickListener());
butPlay.setOnClickListener(controller.newOnPlayButtonClickListener());
butFF.setOnClickListener(controller.newOnFFButtonClickListener());
if (butFF != null) {
butFF.setOnClickListener(controller.newOnFFButtonClickListener());
}
if (butRev != null) {
butRev.setOnClickListener(controller.newOnRevButtonClickListener());
}
butRev.setOnClickListener(controller.newOnRevButtonClickListener());
}
}
protected abstract int getContentViewResourceId();
protected abstract int getContentViewResourceId();
void handleError(int errorCode) {
final AlertDialog.Builder errorDialog = new AlertDialog.Builder(this);
errorDialog.setTitle(R.string.error_label);
errorDialog
.setMessage(MediaPlayerError.getErrorString(this, errorCode));
errorDialog.setNeutralButton("OK",
new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
dialog.dismiss();
finish();
}
});
errorDialog.create().show();
}
void handleError(int errorCode) {
final AlertDialog.Builder errorDialog = new AlertDialog.Builder(this);
errorDialog.setTitle(R.string.error_label);
errorDialog
.setMessage(MediaPlayerError.getErrorString(this, errorCode));
errorDialog.setNeutralButton("OK",
new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
dialog.dismiss();
finish();
}
});
errorDialog.create().show();
}
float prog;
float prog;
@Override
public void onProgressChanged(SeekBar seekBar, int progress,
boolean fromUser) {
prog = controller.onSeekBarProgressChanged(seekBar, progress, fromUser,
txtvPosition);
}
@Override
public void onProgressChanged(SeekBar seekBar, int progress,
boolean fromUser) {
prog = controller.onSeekBarProgressChanged(seekBar, progress, fromUser,
txtvPosition);
}
@Override
public void onStartTrackingTouch(SeekBar seekBar) {
controller.onSeekBarStartTrackingTouch(seekBar);
}
@Override
public void onStartTrackingTouch(SeekBar seekBar) {
controller.onSeekBarStartTrackingTouch(seekBar);
}
@Override
public void onStopTrackingTouch(SeekBar seekBar) {
controller.onSeekBarStopTrackingTouch(seekBar, prog);
}
@Override
public void onStopTrackingTouch(SeekBar seekBar) {
controller.onSeekBarStopTrackingTouch(seekBar, prog);
}
}

View File

@ -28,7 +28,9 @@ import de.danoeh.antennapod.dialog.GpodnetSetHostnameDialog;
import de.danoeh.antennapod.dialog.VariableSpeedDialog;
import de.danoeh.antennapod.preferences.GpodnetPreferences;
import de.danoeh.antennapod.preferences.UserPreferences;
import de.danoeh.antennapod.util.flattr.FlattrStatus;
import de.danoeh.antennapod.util.flattr.FlattrUtils;
import de.danoeh.antennapod.util.flattr.SimpleFlattrThing;
import java.io.File;
import java.util.ArrayList;
@ -44,6 +46,7 @@ public class PreferenceActivity extends android.preference.PreferenceActivity {
private static final String PREF_FLATTR_THIS_APP = "prefFlattrThisApp";
private static final String PREF_FLATTR_AUTH = "pref_flattr_authenticate";
private static final String PREF_FLATTR_REVOKE = "prefRevokeAccess";
private static final String PREF_AUTO_FLATTR = "pref_auto_flattr";
private static final String PREF_OPML_EXPORT = "prefOpmlExport";
private static final String PREF_ABOUT = "prefAbout";
private static final String PREF_CHOOSE_DATA_DIR = "prefChooseDataDir";
@ -78,7 +81,11 @@ public class PreferenceActivity extends android.preference.PreferenceActivity {
@Override
public boolean onPreferenceClick(Preference preference) {
new FlattrClickWorker(PreferenceActivity.this,
FlattrUtils.APP_URL).executeAsync();
new SimpleFlattrThing(PreferenceActivity.this.getString(R.string.app_name),
FlattrUtils.APP_URL,
new FlattrStatus(FlattrStatus.STATUS_QUEUE)
)
).executeAsync();
return true;
}
@ -297,6 +304,7 @@ public class PreferenceActivity extends android.preference.PreferenceActivity {
findPreference(PREF_FLATTR_AUTH).setEnabled(!hasFlattrToken);
findPreference(PREF_FLATTR_REVOKE).setEnabled(hasFlattrToken);
findPreference(PREF_AUTO_FLATTR).setEnabled(hasFlattrToken);
findPreference(UserPreferences.PREF_ENABLE_AUTODL_WIFI_FILTER)
.setEnabled(UserPreferences.isEnableAutodownload());

View File

@ -2,289 +2,338 @@ package de.danoeh.antennapod.activity;
import android.annotation.SuppressLint;
import android.content.Intent;
import android.graphics.drawable.ColorDrawable;
import android.os.AsyncTask;
import android.os.Build;
import android.os.Bundle;
import android.util.Log;
import android.util.Pair;
import android.view.*;
import android.view.animation.Animation;
import android.view.animation.AnimationUtils;
import android.widget.LinearLayout;
import android.widget.ProgressBar;
import android.widget.SeekBar;
import android.widget.VideoView;
import de.danoeh.antennapod.AppConfig;
import de.danoeh.antennapod.R;
import de.danoeh.antennapod.feed.MediaType;
import de.danoeh.antennapod.preferences.UserPreferences;
import de.danoeh.antennapod.service.PlaybackService;
import de.danoeh.antennapod.service.PlayerStatus;
import de.danoeh.antennapod.service.playback.PlaybackService;
import de.danoeh.antennapod.service.playback.PlayerStatus;
import de.danoeh.antennapod.util.playback.ExternalMedia;
import de.danoeh.antennapod.util.playback.Playable;
import de.danoeh.antennapod.view.AspectRatioVideoView;
/** Activity for playing audio files. */
public class VideoplayerActivity extends MediaplayerActivity implements
SurfaceHolder.Callback {
private static final String TAG = "VideoplayerActivity";
/**
* Activity for playing video files.
*/
public class VideoplayerActivity extends MediaplayerActivity {
private static final String TAG = "VideoplayerActivity";
/** True if video controls are currently visible. */
private boolean videoControlsShowing = true;
private boolean videoSurfaceCreated = false;
private VideoControlsHider videoControlsToggler;
/**
* True if video controls are currently visible.
*/
private boolean videoControlsShowing = true;
private boolean videoSurfaceCreated = false;
private VideoControlsHider videoControlsToggler;
private LinearLayout videoOverlay;
private VideoView videoview;
private ProgressBar progressIndicator;
private LinearLayout videoOverlay;
private AspectRatioVideoView videoview;
private ProgressBar progressIndicator;
@Override
protected void onCreate(Bundle savedInstanceState) {
requestWindowFeature(Window.FEATURE_ACTION_BAR_OVERLAY);
setTheme(UserPreferences.getTheme());
@Override
protected void chooseTheme() {
setTheme(R.style.Theme_AntennaPod_Dark);
}
super.onCreate(savedInstanceState);
}
@Override
protected void onCreate(Bundle savedInstanceState) {
if (Build.VERSION.SDK_INT >= 11) {
requestWindowFeature(Window.FEATURE_ACTION_BAR_OVERLAY);
}
super.onCreate(savedInstanceState);
getSupportActionBar().setBackgroundDrawable(new ColorDrawable(0x80000000));
}
@Override
protected void onPause() {
super.onPause();
if (videoControlsToggler != null) {
videoControlsToggler.cancel(true);
}
}
@Override
protected void onPause() {
super.onPause();
if (videoControlsToggler != null) {
videoControlsToggler.cancel(true);
}
if (controller != null && controller.getStatus() == PlayerStatus.PLAYING) {
controller.pause();
}
}
@Override
protected void onResume() {
super.onResume();
if (getIntent().getAction() != null
&& getIntent().getAction().equals(Intent.ACTION_VIEW)) {
Intent intent = getIntent();
if (AppConfig.DEBUG)
Log.d(TAG, "Received VIEW intent: "
+ intent.getData().getPath());
ExternalMedia media = new ExternalMedia(intent.getData().getPath(),
MediaType.VIDEO);
Intent launchIntent = new Intent(this, PlaybackService.class);
launchIntent.putExtra(PlaybackService.EXTRA_PLAYABLE, media);
launchIntent.putExtra(PlaybackService.EXTRA_START_WHEN_PREPARED,
true);
launchIntent.putExtra(PlaybackService.EXTRA_SHOULD_STREAM, false);
launchIntent.putExtra(PlaybackService.EXTRA_PREPARE_IMMEDIATELY,
true);
startService(launchIntent);
}
}
@Override
protected void onResume() {
super.onResume();
if (getIntent().getAction() != null
&& getIntent().getAction().equals(Intent.ACTION_VIEW)) {
Intent intent = getIntent();
if (AppConfig.DEBUG)
Log.d(TAG, "Received VIEW intent: "
+ intent.getData().getPath());
ExternalMedia media = new ExternalMedia(intent.getData().getPath(),
MediaType.VIDEO);
Intent launchIntent = new Intent(this, PlaybackService.class);
launchIntent.putExtra(PlaybackService.EXTRA_PLAYABLE, media);
launchIntent.putExtra(PlaybackService.EXTRA_START_WHEN_PREPARED,
true);
launchIntent.putExtra(PlaybackService.EXTRA_SHOULD_STREAM, false);
launchIntent.putExtra(PlaybackService.EXTRA_PREPARE_IMMEDIATELY,
true);
startService(launchIntent);
}
}
@Override
protected void loadMediaInfo() {
super.loadMediaInfo();
Playable media = controller.getMedia();
if (media != null) {
getSupportActionBar().setSubtitle(media.getEpisodeTitle());
getSupportActionBar().setTitle(media.getFeedTitle());
}
}
@Override
protected boolean loadMediaInfo() {
if (!super.loadMediaInfo()) {
return false;
}
Playable media = controller.getMedia();
if (media != null) {
getSupportActionBar().setSubtitle(media.getEpisodeTitle());
getSupportActionBar().setTitle(media.getFeedTitle());
return true;
}
@Override
protected void setupGUI() {
super.setupGUI();
videoOverlay = (LinearLayout) findViewById(R.id.overlay);
videoview = (VideoView) findViewById(R.id.videoview);
progressIndicator = (ProgressBar) findViewById(R.id.progressIndicator);
videoview.getHolder().addCallback(this);
videoview.setOnTouchListener(onVideoviewTouched);
return false;
}
setupVideoControlsToggler();
getWindow().setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN,
WindowManager.LayoutParams.FLAG_FULLSCREEN);
}
@Override
protected void setupGUI() {
super.setupGUI();
videoOverlay = (LinearLayout) findViewById(R.id.overlay);
videoview = (AspectRatioVideoView) findViewById(R.id.videoview);
progressIndicator = (ProgressBar) findViewById(R.id.progressIndicator);
videoview.getHolder().addCallback(surfaceHolderCallback);
videoview.setOnTouchListener(onVideoviewTouched);
@Override
protected void onAwaitingVideoSurface() {
if (videoSurfaceCreated) {
if (AppConfig.DEBUG)
Log.d(TAG,
"Videosurface already created, setting videosurface now");
controller.setVideoSurface(videoview.getHolder());
}
}
setupVideoControlsToggler();
getWindow().setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN,
WindowManager.LayoutParams.FLAG_FULLSCREEN);
}
@Override
protected void postStatusMsg(int resId) {
if (resId == R.string.player_preparing_msg) {
progressIndicator.setVisibility(View.VISIBLE);
} else {
progressIndicator.setVisibility(View.INVISIBLE);
}
@Override
protected void onAwaitingVideoSurface() {
if (videoSurfaceCreated) {
if (AppConfig.DEBUG)
Log.d(TAG,
"Videosurface already created, setting videosurface now");
}
Pair<Integer, Integer> videoSize = controller.getVideoSize();
if (videoSize != null && videoSize.first > 0 && videoSize.second > 0) {
if (AppConfig.DEBUG) Log.d(TAG, "Width,height of video: " + videoSize.first + ", " + videoSize.second);
videoview.setVideoSize(videoSize.first, videoSize.second);
} else {
Log.e(TAG, "Could not determine video size");
}
controller.setVideoSurface(videoview.getHolder());
}
}
@Override
protected void clearStatusMsg() {
progressIndicator.setVisibility(View.INVISIBLE);
}
@Override
protected void postStatusMsg(int resId) {
if (resId == R.string.player_preparing_msg) {
progressIndicator.setVisibility(View.VISIBLE);
} else {
progressIndicator.setVisibility(View.INVISIBLE);
}
View.OnTouchListener onVideoviewTouched = new View.OnTouchListener() {
}
@Override
public boolean onTouch(View v, MotionEvent event) {
if (event.getAction() == MotionEvent.ACTION_DOWN) {
if (videoControlsToggler != null) {
videoControlsToggler.cancel(true);
}
toggleVideoControlsVisibility();
if (videoControlsShowing) {
setupVideoControlsToggler();
}
@Override
protected void clearStatusMsg() {
progressIndicator.setVisibility(View.INVISIBLE);
}
return true;
} else {
return false;
}
}
};
View.OnTouchListener onVideoviewTouched = new View.OnTouchListener() {
@SuppressLint("NewApi")
void setupVideoControlsToggler() {
if (videoControlsToggler != null) {
videoControlsToggler.cancel(true);
}
videoControlsToggler = new VideoControlsHider();
if (android.os.Build.VERSION.SDK_INT > android.os.Build.VERSION_CODES.GINGERBREAD_MR1) {
videoControlsToggler
.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
} else {
videoControlsToggler.execute();
}
}
@Override
public boolean onTouch(View v, MotionEvent event) {
if (event.getAction() == MotionEvent.ACTION_DOWN) {
if (videoControlsToggler != null) {
videoControlsToggler.cancel(true);
}
toggleVideoControlsVisibility();
if (videoControlsShowing) {
setupVideoControlsToggler();
}
private void toggleVideoControlsVisibility() {
if (videoControlsShowing) {
getSupportActionBar().hide();
hideVideoControls();
} else {
getSupportActionBar().show();
showVideoControls();
}
videoControlsShowing = !videoControlsShowing;
}
return true;
} else {
return false;
}
}
};
/** Hides the videocontrols after a certain period of time. */
public class VideoControlsHider extends AsyncTask<Void, Void, Void> {
@Override
protected void onCancelled() {
videoControlsToggler = null;
}
@SuppressLint("NewApi")
void setupVideoControlsToggler() {
if (videoControlsToggler != null) {
videoControlsToggler.cancel(true);
}
videoControlsToggler = new VideoControlsHider();
if (android.os.Build.VERSION.SDK_INT > android.os.Build.VERSION_CODES.GINGERBREAD_MR1) {
videoControlsToggler
.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
} else {
videoControlsToggler.execute();
}
}
@Override
protected void onPostExecute(Void result) {
videoControlsToggler = null;
}
private void toggleVideoControlsVisibility() {
if (videoControlsShowing) {
getSupportActionBar().hide();
hideVideoControls();
} else {
getSupportActionBar().show();
showVideoControls();
}
videoControlsShowing = !videoControlsShowing;
}
private static final int WAITING_INTERVALL = 5000;
private static final String TAG = "VideoControlsToggler";
/**
* Hides the videocontrols after a certain period of time.
*/
public class VideoControlsHider extends AsyncTask<Void, Void, Void> {
@Override
protected void onCancelled() {
videoControlsToggler = null;
}
@Override
protected void onProgressUpdate(Void... values) {
if (videoControlsShowing) {
if (AppConfig.DEBUG)
Log.d(TAG, "Hiding video controls");
getSupportActionBar().hide();
hideVideoControls();
videoControlsShowing = false;
}
}
@Override
protected void onPostExecute(Void result) {
videoControlsToggler = null;
}
@Override
protected Void doInBackground(Void... params) {
try {
Thread.sleep(WAITING_INTERVALL);
} catch (InterruptedException e) {
return null;
}
publishProgress();
return null;
}
private static final int WAITING_INTERVALL = 5000;
private static final String TAG = "VideoControlsToggler";
}
@Override
protected void onProgressUpdate(Void... values) {
if (videoControlsShowing) {
if (AppConfig.DEBUG)
Log.d(TAG, "Hiding video controls");
getSupportActionBar().hide();
hideVideoControls();
videoControlsShowing = false;
}
}
@Override
public void surfaceChanged(SurfaceHolder holder, int format, int width,
int height) {
holder.setFixedSize(width, height);
}
@Override
protected Void doInBackground(Void... params) {
try {
Thread.sleep(WAITING_INTERVALL);
} catch (InterruptedException e) {
return null;
}
publishProgress();
return null;
}
@Override
public void surfaceCreated(SurfaceHolder holder) {
if (AppConfig.DEBUG)
Log.d(TAG, "Videoview holder created");
videoSurfaceCreated = true;
if (controller.getStatus() == PlayerStatus.AWAITING_VIDEO_SURFACE) {
if (controller.serviceAvailable()) {
controller.setVideoSurface(holder);
} else {
Log.e(TAG,
"Could'nt attach surface to mediaplayer - reference to service was null");
}
}
}
}
private final SurfaceHolder.Callback surfaceHolderCallback = new SurfaceHolder.Callback() {
@Override
public void surfaceChanged(SurfaceHolder holder, int format, int width,
int height) {
holder.setFixedSize(width, height);
}
@Override
public void surfaceDestroyed(SurfaceHolder holder) {
if (AppConfig.DEBUG)
Log.d(TAG, "Videosurface was destroyed");
videoSurfaceCreated = false;
controller.notifyVideoSurfaceAbandoned();
}
@Override
public void surfaceCreated(SurfaceHolder holder) {
if (AppConfig.DEBUG)
Log.d(TAG, "Videoview holder created");
videoSurfaceCreated = true;
if (controller.getStatus() == PlayerStatus.PLAYING) {
if (controller.serviceAvailable()) {
controller.setVideoSurface(holder);
} else {
Log.e(TAG,
"Could'nt attach surface to mediaplayer - reference to service was null");
}
}
@Override
protected void onReloadNotification(int notificationCode) {
if (notificationCode == PlaybackService.EXTRA_CODE_AUDIO) {
if (AppConfig.DEBUG)
Log.d(TAG,
"ReloadNotification received, switching to Audioplayer now");
startActivity(new Intent(this, AudioplayerActivity.class));
}
}
}
@Override
public void onStartTrackingTouch(SeekBar seekBar) {
super.onStartTrackingTouch(seekBar);
if (videoControlsToggler != null) {
videoControlsToggler.cancel(true);
}
}
@Override
public void surfaceDestroyed(SurfaceHolder holder) {
if (AppConfig.DEBUG)
Log.d(TAG, "Videosurface was destroyed");
videoSurfaceCreated = false;
controller.notifyVideoSurfaceAbandoned();
}
};
@Override
public void onStopTrackingTouch(SeekBar seekBar) {
super.onStopTrackingTouch(seekBar);
setupVideoControlsToggler();
}
@Override
protected void onBufferStart() {
progressIndicator.setVisibility(View.VISIBLE);
}
@Override
protected void onReloadNotification(int notificationCode) {
if (notificationCode == PlaybackService.EXTRA_CODE_AUDIO) {
if (AppConfig.DEBUG)
Log.d(TAG,
"ReloadNotification received, switching to Audioplayer now");
finish();
startActivity(new Intent(this, AudioplayerActivity.class));
}
}
@Override
protected void onBufferEnd() {
progressIndicator.setVisibility(View.INVISIBLE);
}
@Override
public void onStartTrackingTouch(SeekBar seekBar) {
super.onStartTrackingTouch(seekBar);
if (videoControlsToggler != null) {
videoControlsToggler.cancel(true);
}
}
private void showVideoControls() {
videoOverlay.setVisibility(View.VISIBLE);
videoOverlay.startAnimation(AnimationUtils.loadAnimation(this,
R.anim.fade_in));
}
@Override
public void onStopTrackingTouch(SeekBar seekBar) {
super.onStopTrackingTouch(seekBar);
setupVideoControlsToggler();
}
private void hideVideoControls() {
videoOverlay.startAnimation(AnimationUtils.loadAnimation(this,
R.anim.fade_out));
videoOverlay.setVisibility(View.GONE);
}
@Override
protected void onBufferStart() {
progressIndicator.setVisibility(View.VISIBLE);
}
@Override
protected int getContentViewResourceId() {
return R.layout.videoplayer_activity;
}
@Override
protected void onBufferEnd() {
progressIndicator.setVisibility(View.INVISIBLE);
}
private void showVideoControls() {
videoOverlay.setVisibility(View.VISIBLE);
butPlay.setVisibility(View.VISIBLE);
final Animation animation = AnimationUtils.loadAnimation(this,
R.anim.fade_in);
if (animation != null) {
videoOverlay.startAnimation(animation);
butPlay.startAnimation(animation);
}
if (Build.VERSION.SDK_INT >= 14) {
videoview.setSystemUiVisibility(View.SYSTEM_UI_FLAG_VISIBLE);
}
}
private void hideVideoControls() {
final Animation animation = AnimationUtils.loadAnimation(this,
R.anim.fade_out);
if (animation != null) {
videoOverlay.startAnimation(animation);
butPlay.startAnimation(animation);
}
if (Build.VERSION.SDK_INT >= 14) {
videoview.setSystemUiVisibility(View.SYSTEM_UI_FLAG_LOW_PROFILE);
}
videoOverlay.setVisibility(View.GONE);
butPlay.setVisibility(View.GONE);
}
@Override
protected int getContentViewResourceId() {
return R.layout.videoplayer_activity;
}
}

View File

@ -56,9 +56,9 @@ public class GpodnetAuthenticationActivity extends ActionBarActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
setTheme(UserPreferences.getTheme());
super.onCreate(savedInstanceState);
getSupportActionBar().setDisplayHomeAsUpEnabled(true);
setTheme(UserPreferences.getTheme());
setContentView(R.layout.gpodnetauth_activity);
service = new GpodnetService();

View File

@ -91,10 +91,12 @@ public class DefaultFeedItemlistAdapter extends BaseAdapter {
MediaType mediaType = item.getMedia().getMediaType();
if (mediaType == MediaType.AUDIO) {
holder.type.setImageDrawable(typeDrawables.getDrawable(0));
holder.type.setContentDescription(context.getString(R.string.media_type_audio_label));
holder.type.setVisibility(View.VISIBLE);
} else if (mediaType == MediaType.VIDEO) {
holder.type.setImageDrawable(typeDrawables.getDrawable(1));
holder.type.setVisibility(View.VISIBLE);
holder.type.setContentDescription(context.getString(R.string.media_type_video_label));
holder.type.setVisibility(View.VISIBLE);
} else {
holder.type.setImageBitmap(null);
holder.type.setVisibility(View.GONE);

View File

@ -148,12 +148,14 @@ public class ExternalEpisodesListAdapter extends BaseExpandableListAdapter {
TypedArray drawables = context.obtainStyledAttributes(new int[] {
R.attr.av_download, R.attr.navigation_refresh });
final int[] labels = new int[] {R.string.status_downloaded_label, R.string.downloading_label};
holder.lenSize.setVisibility(View.VISIBLE);
if (!media.isDownloaded()) {
if (DownloadRequester.getInstance().isDownloadingFile(media)) {
holder.downloadStatus.setVisibility(View.VISIBLE);
holder.downloadStatus.setImageDrawable(drawables
.getDrawable(1));
holder.downloadStatus.setContentDescription(context.getString(labels[1]));
} else {
holder.downloadStatus.setVisibility(View.INVISIBLE);
}
@ -161,6 +163,7 @@ public class ExternalEpisodesListAdapter extends BaseExpandableListAdapter {
holder.downloadStatus.setVisibility(View.VISIBLE);
holder.downloadStatus
.setImageDrawable(drawables.getDrawable(0));
holder.downloadStatus.setContentDescription(context.getString(labels[0]));
}
} else {
holder.downloadStatus.setVisibility(View.INVISIBLE);

View File

@ -176,12 +176,16 @@ public class InternalFeedItemlistAdapter extends DefaultFeedItemlistAdapter {
TypedArray typeDrawables = getContext().obtainStyledAttributes(
new int[] { R.attr.type_audio, R.attr.type_video });
final int[] labels = new int[] {R.string.media_type_audio_label, R.string.media_type_video_label};
MediaType mediaType = item.getMedia().getMediaType();
if (mediaType == MediaType.AUDIO) {
holder.type.setImageDrawable(typeDrawables.getDrawable(0));
holder.type.setContentDescription(getContext().getString(labels[0]));
holder.type.setVisibility(View.VISIBLE);
} else if (mediaType == MediaType.VIDEO) {
holder.type.setImageDrawable(typeDrawables.getDrawable(1));
holder.type.setContentDescription(getContext().getString(labels[1]));
holder.type.setVisibility(View.VISIBLE);
} else {
holder.type.setImageBitmap(null);

View File

@ -0,0 +1,150 @@
package de.danoeh.antennapod.asynctask;
import android.app.Activity;
import android.content.*;
import android.os.Handler;
import android.os.IBinder;
import android.util.Log;
import de.danoeh.antennapod.AppConfig;
import de.danoeh.antennapod.service.download.DownloadService;
import de.danoeh.antennapod.service.download.Downloader;
import java.util.List;
import java.util.concurrent.atomic.AtomicBoolean;
/**
* Provides access to the DownloadService's list of items that are currently being downloaded.
* The DownloadObserver object should be created in the activity's onCreate() method. resume() and pause()
* should be called in the activity's onResume() and onPause() methods
*/
public class DownloadObserver {
private static final String TAG = "DownloadObserver";
/**
* Time period between update notifications.
*/
public static final int WAITING_INTERVAL_MS = 1000;
private final Activity activity;
private final Handler handler;
private final Callback callback;
private DownloadService downloadService = null;
private AtomicBoolean mIsBound = new AtomicBoolean(false);
private Thread refresherThread;
private AtomicBoolean refresherThreadRunning = new AtomicBoolean(false);
/**
* Creates a new download observer.
*
* @param activity Used for registering receivers
* @param handler All callback methods are executed on this handler. The handler MUST run on the GUI thread.
* @param callback Callback methods for posting content updates
* @throws java.lang.IllegalArgumentException if one of the arguments is null.
*/
public DownloadObserver(Activity activity, Handler handler, Callback callback) {
if (activity == null) throw new IllegalArgumentException("activity = null");
if (handler == null) throw new IllegalArgumentException("handler = null");
if (callback == null) throw new IllegalArgumentException("callback = null");
this.activity = activity;
this.handler = handler;
this.callback = callback;
}
public void onResume() {
if (AppConfig.DEBUG) Log.d(TAG, "DownloadObserver resumed");
activity.registerReceiver(contentChangedReceiver, new IntentFilter(DownloadService.ACTION_DOWNLOADS_CONTENT_CHANGED));
activity.bindService(new Intent(activity, DownloadService.class), mConnection, 0);
}
public void onPause() {
if (AppConfig.DEBUG) Log.d(TAG, "DownloadObserver paused");
activity.unregisterReceiver(contentChangedReceiver);
activity.unbindService(mConnection);
stopRefresher();
}
private BroadcastReceiver contentChangedReceiver = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
callback.onContentChanged();
startRefresher();
}
};
public interface Callback {
void onContentChanged();
void onDownloadDataAvailable(List<Downloader> downloaderList);
}
private ServiceConnection mConnection = new ServiceConnection() {
public void onServiceDisconnected(ComponentName className) {
downloadService = null;
mIsBound.set(false);
stopRefresher();
Log.i(TAG, "Closed connection with DownloadService.");
}
public void onServiceConnected(ComponentName name, IBinder service) {
downloadService = ((DownloadService.LocalBinder) service)
.getService();
mIsBound.set(true);
if (AppConfig.DEBUG)
Log.d(TAG, "Connection to service established");
List<Downloader> downloaderList = downloadService.getDownloads();
if (downloaderList != null && !downloaderList.isEmpty()) {
callback.onDownloadDataAvailable(downloaderList);
startRefresher();
}
}
};
private void stopRefresher() {
if (refresherThread != null) {
refresherThread.interrupt();
}
}
private void startRefresher() {
if (refresherThread == null || refresherThread.isInterrupted()) {
refresherThread = new Thread(new RefresherThread());
refresherThread.start();
}
}
private class RefresherThread implements Runnable {
public void run() {
refresherThreadRunning.set(true);
while (!Thread.interrupted()) {
try {
Thread.sleep(WAITING_INTERVAL_MS);
} catch (InterruptedException e) {
Log.d(TAG, "Refresher thread was interrupted");
}
if (mIsBound.get()) {
postUpdate();
}
}
refresherThreadRunning.set(false);
}
private void postUpdate() {
handler.post(new Runnable() {
@Override
public void run() {
callback.onContentChanged();
List<Downloader> downloaderList = downloadService.getDownloads();
if (downloaderList == null || downloaderList.isEmpty()) {
Thread.currentThread().interrupt();
}
}
});
}
}
}

View File

@ -1,115 +1,308 @@
package de.danoeh.antennapod.asynctask;
import org.shredzone.flattr4j.exception.FlattrException;
import android.annotation.SuppressLint;
import android.app.Notification;
import android.app.NotificationManager;
import android.app.ProgressDialog;
import android.content.Context;
import android.net.ConnectivityManager;
import android.net.NetworkInfo;
import android.os.AsyncTask;
import android.support.v4.app.NotificationCompat;
import android.util.Log;
import android.widget.Toast;
import de.danoeh.antennapod.AppConfig;
import de.danoeh.antennapod.R;
import de.danoeh.antennapod.storage.DBReader;
import de.danoeh.antennapod.storage.DBWriter;
import de.danoeh.antennapod.util.flattr.FlattrThing;
import de.danoeh.antennapod.util.flattr.FlattrUtils;
/** Performs a click action in a background thread. */
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.Executor;
public class FlattrClickWorker extends AsyncTask<Void, Void, Void> {
protected static final String TAG = "FlattrClickWorker";
protected Context context;
protected String url;
protected String errorMsg;
protected int exitCode;
protected ProgressDialog progDialog;
/**
* Performs a click action in a background thread.
*/
protected final static int SUCCESS = 0;
protected final static int NO_TOKEN = 1;
protected final static int FLATTR_ERROR = 2;
public class FlattrClickWorker extends AsyncTask<Void, String, Void> {
protected static final String TAG = "FlattrClickWorker";
protected Context context;
public FlattrClickWorker(Context context, String url) {
super();
this.context = context;
this.url = url;
exitCode = SUCCESS;
errorMsg = "";
}
private final int NOTIFICATION_ID = 4;
protected void onNoAccessToken() {
Log.w(TAG, "No access token was available");
if (url.equals(FlattrUtils.APP_URL)) {
FlattrUtils.showNoTokenDialog(context, FlattrUtils.APP_LINK);
} else {
FlattrUtils.showNoTokenDialog(context, url);
}
}
protected String errorMsg;
protected int exitCode;
protected ArrayList<String> flattrd;
protected ArrayList<String> flattr_failed;
protected void onFlattrError() {
FlattrUtils.showErrorDialog(context, errorMsg);
}
protected void onSuccess() {
Toast toast = Toast.makeText(context.getApplicationContext(),
R.string.flattr_click_success, Toast.LENGTH_LONG);
toast.show();
}
protected NotificationCompat.Builder notificationCompatBuilder;
private Notification.BigTextStyle notificationBuilder;
protected NotificationManager notificationManager;
protected void onSetupProgDialog() {
progDialog = new ProgressDialog(context);
progDialog.setMessage(context.getString(R.string.flattring_label));
progDialog.setIndeterminate(true);
progDialog.setCancelable(false);
progDialog.show();
}
protected ProgressDialog progDialog;
@Override
protected void onPostExecute(Void result) {
if (AppConfig.DEBUG) Log.d(TAG, "Exit code was " + exitCode);
if (progDialog != null) {
progDialog.dismiss();
}
switch (exitCode) {
case NO_TOKEN:
onNoAccessToken();
break;
case FLATTR_ERROR:
onFlattrError();
break;
case SUCCESS:
onSuccess();
break;
}
}
protected final static int EXIT_DEFAULT = 0;
protected final static int NO_TOKEN = 1;
protected final static int ENQUEUED = 2;
protected final static int NO_THINGS = 3;
@Override
protected void onPreExecute() {
onSetupProgDialog();
}
public final static int ENQUEUE_ONLY = 1;
public final static int FLATTR_TOAST = 2;
public static final int FLATTR_NOTIFICATION = 3;
@Override
protected Void doInBackground(Void... params) {
if (AppConfig.DEBUG) Log.d(TAG, "Starting background work");
if (FlattrUtils.hasToken()) {
try {
FlattrUtils.clickUrl(context, url);
} catch (FlattrException e) {
e.printStackTrace();
exitCode = FLATTR_ERROR;
errorMsg = e.getMessage();
}
} else {
exitCode = NO_TOKEN;
}
return null;
}
private int run_mode = FLATTR_NOTIFICATION;
private FlattrThing extra_flattr_thing; // additional urls to flattr that do *not* originate from the queue
/**
* @param context
* @param run_mode can be one of ENQUEUE_ONLY, FLATTR_TOAST and FLATTR_NOTIFICATION
*/
public FlattrClickWorker(Context context, int run_mode) {
this(context);
this.run_mode = run_mode;
}
public FlattrClickWorker(Context context) {
super();
this.context = context;
exitCode = EXIT_DEFAULT;
flattrd = new ArrayList<String>();
flattr_failed = new ArrayList<String>();
errorMsg = "";
}
/* only used in PreferencesActivity for flattring antennapod itself,
* can't really enqueue this thing
*/
public FlattrClickWorker(Context context, FlattrThing thing) {
this(context);
extra_flattr_thing = thing;
run_mode = FLATTR_TOAST;
Log.d(TAG, "Going to flattr special thing that is not in the queue: " + thing.getTitle());
}
protected void onNoAccessToken() {
Log.w(TAG, "No access token was available");
}
protected void onFlattrError() {
FlattrUtils.showErrorDialog(context, errorMsg);
}
protected void onFlattred() {
String notificationTitle = context.getString(R.string.flattrd_label);
String notificationText = "", notificationSubText = "", notificationBigText = "";
// text for successfully flattred items
if (flattrd.size() == 1)
notificationText = String.format(context.getString(R.string.flattr_click_success));
else if (flattrd.size() > 1) // flattred pending items from queue
notificationText = String.format(context.getString(R.string.flattr_click_success_count, flattrd.size()));
if (flattrd.size() > 0) {
String acc = "";
for (String s : flattrd)
acc += s + '\n';
acc = acc.substring(0, acc.length() - 2);
notificationBigText = String.format(context.getString(R.string.flattr_click_success_queue), acc);
}
// add text for failures
if (flattr_failed.size() > 0) {
notificationTitle = context.getString(R.string.flattrd_failed_label);
notificationText = String.format(context.getString(R.string.flattr_click_failure_count), flattr_failed.size())
+ " " + notificationText;
notificationSubText = flattr_failed.get(0);
String acc = "";
for (String s : flattr_failed)
acc += s + '\n';
acc = acc.substring(0, acc.length() - 2);
notificationBigText = String.format(context.getString(R.string.flattr_click_failure), acc)
+ "\n" + notificationBigText;
}
Log.d(TAG, "Going to post notification: " + notificationBigText);
notificationManager.cancel(NOTIFICATION_ID);
if (run_mode == FLATTR_NOTIFICATION || flattr_failed.size() > 0) {
if (android.os.Build.VERSION.SDK_INT >= 16) {
notificationBuilder = new Notification.BigTextStyle(
new Notification.Builder(context)
.setOngoing(false)
.setContentTitle(notificationTitle)
.setContentText(notificationText)
.setSubText(notificationSubText)
.setSmallIcon(R.drawable.stat_notify_sync))
.bigText(notificationText + "\n" + notificationBigText);
notificationManager.notify(NOTIFICATION_ID, notificationBuilder.build());
} else {
notificationCompatBuilder = new NotificationCompat.Builder(context) // need new notificationBuilder and cancel/renotify to get rid of progress bar
.setContentTitle(notificationTitle)
.setContentText(notificationText)
.setSubText(notificationBigText)
.setTicker(notificationTitle)
.setSmallIcon(R.drawable.stat_notify_sync)
.setOngoing(false);
notificationManager.notify(NOTIFICATION_ID, notificationCompatBuilder.build());
}
} else if (run_mode == FLATTR_TOAST) {
Toast.makeText(context.getApplicationContext(),
notificationText,
Toast.LENGTH_LONG)
.show();
}
}
protected void onEnqueue() {
Toast.makeText(context.getApplicationContext(),
R.string.flattr_click_enqueued,
Toast.LENGTH_LONG)
.show();
}
protected void onSetupNotification() {
if (android.os.Build.VERSION.SDK_INT >= 16) {
notificationBuilder = new Notification.BigTextStyle(
new Notification.Builder(context)
.setContentTitle(context.getString(R.string.flattring_label))
.setAutoCancel(true)
.setSmallIcon(R.drawable.stat_notify_sync)
.setProgress(0, 0, true)
.setOngoing(true));
} else {
notificationCompatBuilder = new NotificationCompat.Builder(context)
.setContentTitle(context.getString(R.string.flattring_label))
.setAutoCancel(true)
.setSmallIcon(R.drawable.stat_notify_sync)
.setProgress(0, 0, true)
.setOngoing(true);
}
notificationManager = (NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE);
}
@Override
protected void onPostExecute(Void result) {
if (AppConfig.DEBUG) Log.d(TAG, "Exit code was " + exitCode);
switch (exitCode) {
case NO_TOKEN:
notificationManager.cancel(NOTIFICATION_ID);
onNoAccessToken();
break;
case ENQUEUED:
onEnqueue();
break;
case EXIT_DEFAULT:
onFlattred();
break;
case NO_THINGS: // FlattrClickWorker called automatically somewhere to empty flattr queue
notificationManager.cancel(NOTIFICATION_ID);
break;
}
}
@Override
protected void onPreExecute() {
onSetupNotification();
}
private static boolean haveInternetAccess(Context context) {
ConnectivityManager cm =
(ConnectivityManager) context.getSystemService(Context.CONNECTIVITY_SERVICE);
NetworkInfo networkInfo = cm.getActiveNetworkInfo();
return (networkInfo != null && networkInfo.isConnectedOrConnecting());
}
@Override
protected Void doInBackground(Void... params) {
if (AppConfig.DEBUG) Log.d(TAG, "Starting background work");
exitCode = EXIT_DEFAULT;
if (!FlattrUtils.hasToken()) {
exitCode = NO_TOKEN;
} else if (DBReader.getFlattrQueueEmpty(context) && extra_flattr_thing == null) {
exitCode = NO_THINGS;
} else if (!haveInternetAccess(context) || run_mode == ENQUEUE_ONLY) {
exitCode = ENQUEUED;
} else {
List<FlattrThing> flattrList = DBReader.getFlattrQueue(context);
Log.d(TAG, "flattrQueue processing list with " + flattrList.size() + " items.");
if (extra_flattr_thing != null)
flattrList.add(extra_flattr_thing);
flattrd.ensureCapacity(flattrList.size());
for (FlattrThing thing : flattrList) {
try {
Log.d(TAG, "flattrQueue processing " + thing.getTitle() + " " + thing.getPaymentLink());
publishProgress(String.format(context.getString(R.string.flattring_thing), thing.getTitle()));
thing.getFlattrStatus().setUnflattred(); // pop from queue to prevent unflattrable things from getting stuck in flattr queue infinitely
FlattrUtils.clickUrl(context, thing.getPaymentLink());
flattrd.add(thing.getTitle());
thing.getFlattrStatus().setFlattred();
} catch (Exception e) {
Log.d(TAG, "flattrQueue processing exception at item " + thing.getTitle() + " " + e.getMessage());
flattr_failed.ensureCapacity(flattrList.size());
flattr_failed.add(thing.getTitle() + ": " + e.getMessage());
}
Log.d(TAG, "flattrQueue processing - going to write thing back to db with flattr_status " + Long.toString(thing.getFlattrStatus().toLong()));
DBWriter.setFlattredStatus(context, thing, false);
}
}
return null;
}
@Override
protected void onProgressUpdate(String... names) {
if (android.os.Build.VERSION.SDK_INT >= 16) {
notificationBuilder.setBigContentTitle(names[0]);
notificationManager.notify(NOTIFICATION_ID, notificationBuilder.build());
} else {
notificationCompatBuilder.setContentText(names[0]);
notificationManager.notify(NOTIFICATION_ID, notificationCompatBuilder.build());
}
}
@SuppressLint("NewApi")
public void executeAsync() {
FlattrUtils.hasToken();
if (android.os.Build.VERSION.SDK_INT > android.os.Build.VERSION_CODES.GINGERBREAD_MR1) {
executeOnExecutor(THREAD_POOL_EXECUTOR);
} else {
execute();
}
}
public void executeSync() {
class DirectExecutor implements Executor {
public void execute(Runnable r) {
r.run();
}
}
FlattrUtils.hasToken();
executeOnExecutor(new DirectExecutor());
}
@SuppressLint("NewApi")
public void executeAsync() {
FlattrUtils.hasToken();
if (android.os.Build.VERSION.SDK_INT > android.os.Build.VERSION_CODES.GINGERBREAD_MR1) {
executeOnExecutor(THREAD_POOL_EXECUTOR);
} else {
execute();
}
}
}

View File

@ -0,0 +1,47 @@
package de.danoeh.antennapod.asynctask;
import android.content.Context;
import android.util.Log;
import de.danoeh.antennapod.AppConfig;
import de.danoeh.antennapod.storage.DBWriter;
import de.danoeh.antennapod.util.flattr.FlattrUtils;
import org.shredzone.flattr4j.exception.FlattrException;
import org.shredzone.flattr4j.model.Flattr;
import java.util.List;
import java.util.concurrent.ExecutionException;
/**
* Fetch list of flattred things and flattr status in database in a background thread.
*/
public class FlattrStatusFetcher extends Thread {
protected static final String TAG = "FlattrStatusFetcher";
protected Context context;
public FlattrStatusFetcher(Context context) {
super();
this.context = context;
}
@Override
public void run() {
if (AppConfig.DEBUG) Log.d(TAG, "Starting background work: Retrieving Flattr status");
Thread.currentThread().setPriority(Thread.MIN_PRIORITY);
try {
List<Flattr> flattredThings = FlattrUtils.retrieveFlattredThings();
DBWriter.setFlattredStatus(context, flattredThings).get();
} catch (FlattrException e) {
e.printStackTrace();
Log.d(TAG, "flattrQueue exception retrieving list with flattred items " + e.getMessage());
} catch (InterruptedException e) {
e.printStackTrace();
} catch (ExecutionException e) {
e.printStackTrace();
}
if (AppConfig.DEBUG) Log.d(TAG, "Finished background work: Retrieved Flattr status");
}
}

View File

@ -10,13 +10,15 @@ import java.util.List;
import de.danoeh.antennapod.preferences.UserPreferences;
import de.danoeh.antennapod.storage.DBWriter;
import de.danoeh.antennapod.util.EpisodeFilter;
import de.danoeh.antennapod.util.flattr.FlattrStatus;
import de.danoeh.antennapod.util.flattr.FlattrThing;
/**
* Data Object for a whole feed
*
* @author daniel
*/
public class Feed extends FeedFile {
public class Feed extends FeedFile implements FlattrThing {
public static final int FEEDFILETYPE_FEED = 0;
public static final String TYPE_RSS2 = "rss";
public static final String TYPE_RSS091 = "rss";
@ -43,6 +45,7 @@ public class Feed extends FeedFile {
* Date of last refresh.
*/
private Date lastUpdate;
private FlattrStatus flattrStatus;
private String paymentLink;
/**
* Feed type, for example RSS 2 or Atom
@ -59,7 +62,7 @@ public class Feed extends FeedFile {
*/
public Feed(long id, Date lastUpdate, String title, String link, String description, String paymentLink,
String author, String language, String type, String feedIdentifier, FeedImage image, String fileUrl,
String downloadUrl, boolean downloaded) {
String downloadUrl, boolean downloaded, FlattrStatus status) {
super(fileUrl, downloadUrl, downloaded);
this.id = id;
this.title = title;
@ -76,10 +79,21 @@ public class Feed extends FeedFile {
this.type = type;
this.feedIdentifier = feedIdentifier;
this.image = image;
this.flattrStatus = status;
items = new ArrayList<FeedItem>();
}
/**
* This constructor is used for test purposes and uses a default flattr status object.
*/
public Feed(long id, Date lastUpdate, String title, String link, String description, String paymentLink,
String author, String language, String type, String feedIdentifier, FeedImage image, String fileUrl,
String downloadUrl, boolean downloaded) {
this(id, lastUpdate, title, link, description, paymentLink, author, language, type, feedIdentifier, image,
fileUrl, downloadUrl, downloaded, new FlattrStatus());
}
/**
* This constructor can be used when parsing feed data. Only the 'lastUpdate' and 'items' field are initialized.
*/
@ -87,6 +101,7 @@ public class Feed extends FeedFile {
super();
items = new ArrayList<FeedItem>();
lastUpdate = new Date();
this.flattrStatus = new FlattrStatus();
}
/**
@ -96,6 +111,7 @@ public class Feed extends FeedFile {
public Feed(String url, Date lastUpdate) {
super(null, url, false);
this.lastUpdate = (lastUpdate != null) ? (Date) lastUpdate.clone() : null;
this.flattrStatus = new FlattrStatus();
}
/**
@ -105,6 +121,7 @@ public class Feed extends FeedFile {
public Feed(String url, Date lastUpdate, String title) {
this(url, lastUpdate);
this.title = title;
this.flattrStatus = new FlattrStatus();
}
/**
@ -238,6 +255,9 @@ public class Feed extends FeedFile {
if (other.paymentLink != null) {
paymentLink = other.paymentLink;
}
if (other.flattrStatus != null) {
flattrStatus = other.flattrStatus;
}
}
public boolean compareWithOther(Feed other) {
@ -342,6 +362,14 @@ public class Feed extends FeedFile {
this.feedIdentifier = feedIdentifier;
}
public void setFlattrStatus(FlattrStatus status) {
this.flattrStatus = status;
}
public FlattrStatus getFlattrStatus() {
return flattrStatus;
}
public String getPaymentLink() {
return paymentLink;
}

View File

@ -10,6 +10,8 @@ import de.danoeh.antennapod.PodcastApp;
import de.danoeh.antennapod.asynctask.ImageLoader;
import de.danoeh.antennapod.storage.DBReader;
import de.danoeh.antennapod.util.ShownotesProvider;
import de.danoeh.antennapod.util.flattr.FlattrStatus;
import de.danoeh.antennapod.util.flattr.FlattrThing;
/**
* Data Object for a XML message
@ -17,7 +19,7 @@ import de.danoeh.antennapod.util.ShownotesProvider;
* @author daniel
*/
public class FeedItem extends FeedComponent implements
ImageLoader.ImageWorkerTaskResource, ShownotesProvider {
ImageLoader.ImageWorkerTaskResource, ShownotesProvider, FlattrThing {
/**
* The id/guid that can be found in the rss/atom feed. Might not be set.
@ -42,10 +44,12 @@ public class FeedItem extends FeedComponent implements
private boolean read;
private String paymentLink;
private FlattrStatus flattrStatus;
private List<Chapter> chapters;
public FeedItem() {
this.read = true;
this.flattrStatus = new FlattrStatus();
}
/**
@ -59,6 +63,7 @@ public class FeedItem extends FeedComponent implements
this.pubDate = (pubDate != null) ? (Date) pubDate.clone() : null;
this.read = read;
this.feed = feed;
this.flattrStatus = new FlattrStatus();
}
public void updateFromOther(FeedItem other) {
@ -80,7 +85,7 @@ public class FeedItem extends FeedComponent implements
}
if (other.media != null) {
if (media == null) {
media = other.media;
setMedia(other.media);
} else if (media.compareWithOther(other)) {
media.updateFromOther(other);
}
@ -102,9 +107,9 @@ public class FeedItem extends FeedComponent implements
* of the entry.
*/
public String getIdentifyingValue() {
if (itemIdentifier != null) {
if (itemIdentifier != null && !itemIdentifier.isEmpty()) {
return itemIdentifier;
} else if (title != null) {
} else if (title != null && !title.isEmpty()) {
return title;
} else {
return link;
@ -195,7 +200,15 @@ public class FeedItem extends FeedComponent implements
this.contentEncoded = contentEncoded;
}
public String getPaymentLink() {
public void setFlattrStatus(FlattrStatus status) {
this.flattrStatus = status;
}
public FlattrStatus getFlattrStatus() {
return flattrStatus;
}
public String getPaymentLink() {
return paymentLink;
}

View File

@ -1,15 +1,11 @@
package de.danoeh.antennapod.feed;
import java.io.FileInputStream;
import java.io.InputStream;
import java.util.Date;
import java.util.List;
import java.util.concurrent.Callable;
import android.content.SharedPreferences;
import android.content.SharedPreferences.Editor;
import android.os.Parcel;
import android.os.Parcelable;
import android.util.Log;
import de.danoeh.antennapod.AppConfig;
import de.danoeh.antennapod.PodcastApp;
import de.danoeh.antennapod.preferences.PlaybackPreferences;
import de.danoeh.antennapod.storage.DBReader;
@ -17,7 +13,14 @@ import de.danoeh.antennapod.storage.DBWriter;
import de.danoeh.antennapod.util.ChapterUtils;
import de.danoeh.antennapod.util.playback.Playable;
import java.io.FileInputStream;
import java.io.InputStream;
import java.util.Date;
import java.util.List;
import java.util.concurrent.Callable;
public class FeedMedia extends FeedFile implements Playable {
private static final String TAG = "FeedMedia";
public static final int FEEDFILETYPE_FEEDMEDIA = 2;
public static final int PLAYABLE_TYPE_FEEDMEDIA = 1;
@ -27,6 +30,7 @@ public class FeedMedia extends FeedFile implements Playable {
private int duration;
private int position; // Current position in file
private int played_duration; // How many ms of this file have been played (for autoflattring)
private long size; // File size in Byte
private String mime_type;
private volatile FeedItem item;
@ -45,12 +49,13 @@ public class FeedMedia extends FeedFile implements Playable {
public FeedMedia(long id, FeedItem item, int duration, int position,
long size, String mime_type, String file_url, String download_url,
boolean downloaded, Date playbackCompletionDate) {
boolean downloaded, Date playbackCompletionDate, int played_duration) {
super(file_url, download_url, downloaded);
this.id = id;
this.item = item;
this.duration = duration;
this.position = position;
this.played_duration = played_duration;
this.size = size;
this.mime_type = mime_type;
this.playbackCompletionDate = playbackCompletionDate == null
@ -137,6 +142,14 @@ public class FeedMedia extends FeedFile implements Playable {
this.duration = duration;
}
public int getPlayedDuration() {
return played_duration;
}
public void setPlayedDuration(int played_duration) {
this.played_duration = played_duration;
}
public int getPosition() {
return position;
}
@ -169,7 +182,7 @@ public class FeedMedia extends FeedFile implements Playable {
* Sets the item object of this FeedMedia. If the given
* FeedItem object is not null, it's 'media'-attribute value
* will also be set to this media object.
* */
*/
public void setItem(FeedItem item) {
this.item = item;
if (item != null && item.getMedia() != this) {
@ -179,7 +192,8 @@ public class FeedMedia extends FeedFile implements Playable {
public Date getPlaybackCompletionDate() {
return playbackCompletionDate == null
? null : (Date) playbackCompletionDate.clone(); }
? null : (Date) playbackCompletionDate.clone();
}
public void setPlaybackCompletionDate(Date playbackCompletionDate) {
this.playbackCompletionDate = playbackCompletionDate == null
@ -215,6 +229,7 @@ public class FeedMedia extends FeedFile implements Playable {
dest.writeString(download_url);
dest.writeByte((byte) ((downloaded) ? 1 : 0));
dest.writeLong((playbackCompletionDate != null) ? playbackCompletionDate.getTime() : 0);
dest.writeInt(played_duration);
}
@Override
@ -313,7 +328,7 @@ public class FeedMedia extends FeedFile implements Playable {
@Override
public void saveCurrentPosition(SharedPreferences pref, int newPosition) {
position = newPosition;
setPosition(newPosition);
DBWriter.setFeedMediaPlaybackInformation(PodcastApp.getInstance(), this);
}
@ -358,7 +373,7 @@ public class FeedMedia extends FeedFile implements Playable {
final long id = in.readLong();
final long itemID = in.readLong();
FeedMedia result = new FeedMedia(id, null, in.readInt(), in.readInt(), in.readLong(), in.readString(), in.readString(),
in.readString(), in.readByte() != 0, new Date(in.readLong()));
in.readString(), in.readByte() != 0, new Date(in.readLong()), in.readInt());
result.itemID = itemID;
return result;
}

View File

@ -14,7 +14,7 @@ import android.widget.TextView;
import de.danoeh.antennapod.AppConfig;
import de.danoeh.antennapod.R;
import de.danoeh.antennapod.asynctask.ImageLoader;
import de.danoeh.antennapod.service.PlaybackService;
import de.danoeh.antennapod.service.playback.PlaybackService;
import de.danoeh.antennapod.util.Converter;
import de.danoeh.antennapod.util.playback.Playable;
import de.danoeh.antennapod.util.playback.PlaybackController;
@ -137,10 +137,12 @@ public class ExternalPlayerFragment extends Fragment {
}
@Override
public void loadMediaInfo() {
public boolean loadMediaInfo() {
ExternalPlayerFragment fragment = ExternalPlayerFragment.this;
if (fragment != null) {
fragment.loadMediaInfo();
return fragment.loadMediaInfo();
} else {
return false;
}
}
@ -209,7 +211,7 @@ public class ExternalPlayerFragment extends Fragment {
}
}
private void loadMediaInfo() {
private boolean loadMediaInfo() {
if (AppConfig.DEBUG)
Log.d(TAG, "Loading media info");
if (controller.serviceAvailable()) {
@ -230,13 +232,16 @@ public class ExternalPlayerFragment extends Fragment {
} else {
butPlay.setVisibility(View.VISIBLE);
}
return true;
} else {
Log.w(TAG,
"loadMediaInfo was called while the media object of playbackService was null!");
return false;
}
} else {
Log.w(TAG,
"loadMediaInfo was called while playbackService was null!");
return false;
}
}

View File

@ -244,19 +244,11 @@ public class FeedlistFragment extends Fragment implements
return true;
}
private boolean actionModeDestroyWorkaround = false; // TODO remove this workaround
private boolean skipWorkAround = Build.VERSION.SDK_INT < Build.VERSION_CODES.ICE_CREAM_SANDWICH;
@Override
public void onDestroyActionMode(ActionMode mode) {
if (skipWorkAround || actionModeDestroyWorkaround) {
mActionMode = null;
selectedFeed = null;
fla.setSelectedItemIndex(FeedlistAdapter.SELECTION_NONE);
actionModeDestroyWorkaround = false;
} else {
actionModeDestroyWorkaround = true;
}
mActionMode = null;
selectedFeed = null;
fla.setSelectedItemIndex(FeedlistAdapter.SELECTION_NONE);
}
@Override

View File

@ -1,35 +0,0 @@
package de.danoeh.antennapod.gpoddernet;
import org.apache.http.conn.ClientConnectionManager;
import org.apache.http.conn.scheme.PlainSocketFactory;
import org.apache.http.conn.scheme.Scheme;
import org.apache.http.conn.scheme.SchemeRegistry;
import org.apache.http.conn.ssl.SSLSocketFactory;
import org.apache.http.impl.client.DefaultHttpClient;
import org.apache.http.impl.conn.tsccm.ThreadSafeClientConnManager;
import org.apache.http.params.BasicHttpParams;
/**
* HTTP client for the gpodder.net service.
*/
public class GpodnetClient extends DefaultHttpClient {
private static SchemeRegistry prepareSchemeRegistry() {
SchemeRegistry sr = new SchemeRegistry();
Scheme http = new Scheme("http",
PlainSocketFactory.getSocketFactory(), 80);
sr.register(http);
Scheme https = new Scheme("https",
SSLSocketFactory.getSocketFactory(), 443);
sr.register(https);
return sr;
}
@Override
protected ClientConnectionManager createClientConnectionManager() {
return new ThreadSafeClientConnManager(new BasicHttpParams(), prepareSchemeRegistry());
}
}

View File

@ -1,9 +1,8 @@
package de.danoeh.antennapod.gpoddernet;
import de.danoeh.antennapod.AppConfig;
import de.danoeh.antennapod.gpoddernet.model.*;
import de.danoeh.antennapod.preferences.GpodnetPreferences;
import de.danoeh.antennapod.preferences.UserPreferences;
import de.danoeh.antennapod.service.download.AntennapodHttpClient;
import org.apache.http.Header;
import org.apache.http.HttpEntity;
import org.apache.http.HttpResponse;
@ -11,15 +10,13 @@ import org.apache.http.HttpStatus;
import org.apache.http.auth.AuthenticationException;
import org.apache.http.auth.UsernamePasswordCredentials;
import org.apache.http.client.ClientProtocolException;
import org.apache.http.client.HttpClient;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.client.methods.HttpPut;
import org.apache.http.client.methods.HttpRequestBase;
import org.apache.http.entity.StringEntity;
import org.apache.http.impl.auth.BasicScheme;
import org.apache.http.params.CoreProtocolPNames;
import org.apache.http.params.HttpConnectionParams;
import org.apache.http.params.HttpParams;
import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;
@ -45,16 +42,10 @@ public class GpodnetService {
public static final String DEFAULT_BASE_HOST = "gpodder.net";
private final String BASE_HOST;
private static final int TIMEOUT_MILLIS = 20000;
private final GpodnetClient httpClient;
private final HttpClient httpClient;
public GpodnetService() {
httpClient = new GpodnetClient();
final HttpParams params = httpClient.getParams();
params.setParameter(CoreProtocolPNames.USER_AGENT, AppConfig.USER_AGENT);
HttpConnectionParams.setConnectionTimeout(params, TIMEOUT_MILLIS);
HttpConnectionParams.setSoTimeout(params, TIMEOUT_MILLIS);
httpClient = AntennapodHttpClient.getHttpClient();
BASE_HOST = GpodnetPreferences.getHostname();
}
@ -519,7 +510,7 @@ public class GpodnetService {
new Thread() {
@Override
public void run() {
httpClient.getConnectionManager().shutdown();
AntennapodHttpClient.cleanup();
}
}.start();
}

View File

@ -40,6 +40,7 @@ public class UserPreferences implements
public static final String PREF_MOBILE_UPDATE = "prefMobileUpdate";
public static final String PREF_DISPLAY_ONLY_EPISODES = "prefDisplayOnlyEpisodes";
public static final String PREF_AUTO_DELETE = "prefAutoDelete";
public static final String PREF_AUTO_FLATTR = "pref_auto_flattr";
public static final String PREF_THEME = "prefTheme";
public static final String PREF_DATA_FOLDER = "prefDataFolder";
public static final String PREF_ENABLE_AUTODL = "prefEnableAutoDl";
@ -50,6 +51,9 @@ public class UserPreferences implements
private static final String PREF_PLAYBACK_SPEED_ARRAY = "prefPlaybackSpeedArray";
public static final String PREF_PAUSE_PLAYBACK_FOR_FOCUS_LOSS = "prefPauseForFocusLoss";
// TODO: Make this value configurable
private static final double PLAYED_DURATION_AUTOFLATTR_THRESHOLD = 0.8;
private static int EPISODE_CACHE_SIZE_UNLIMITED = -1;
private static UserPreferences instance;
@ -63,6 +67,7 @@ public class UserPreferences implements
private boolean allowMobileUpdate;
private boolean displayOnlyEpisodes;
private boolean autoDelete;
private boolean autoFlattr;
private int theme;
private boolean enableAutodownload;
private boolean enableAutodownloadWifiFilter;
@ -112,6 +117,7 @@ public class UserPreferences implements
allowMobileUpdate = sp.getBoolean(PREF_MOBILE_UPDATE, false);
displayOnlyEpisodes = sp.getBoolean(PREF_DISPLAY_ONLY_EPISODES, false);
autoDelete = sp.getBoolean(PREF_AUTO_DELETE, false);
autoFlattr = sp.getBoolean(PREF_AUTO_FLATTR, false);
theme = readThemeValue(sp.getString(PREF_THEME, "0"));
enableAutodownloadWifiFilter = sp.getBoolean(
PREF_ENABLE_AUTODL_WIFI_FILTER, false);
@ -223,6 +229,11 @@ public class UserPreferences implements
instanceAvailable();
return instance.autoDelete;
}
public static boolean isAutoFlattr() {
instanceAvailable();
return instance.autoFlattr;
}
public static int getTheme() {
instanceAvailable();
@ -296,6 +307,8 @@ public class UserPreferences implements
} else if (key.equals(PREF_AUTO_DELETE)) {
autoDelete = sp.getBoolean(PREF_AUTO_DELETE, false);
} else if (key.equals(PREF_AUTO_FLATTR)) {
autoFlattr = sp.getBoolean(PREF_AUTO_FLATTR, false);
} else if (key.equals(PREF_DISPLAY_ONLY_EPISODES)) {
displayOnlyEpisodes = sp.getBoolean(PREF_DISPLAY_ONLY_EPISODES,
false);
@ -319,7 +332,9 @@ public class UserPreferences implements
PREF_PLAYBACK_SPEED_ARRAY, null));
} else if (key.equals(PREF_PAUSE_PLAYBACK_FOR_FOCUS_LOSS)) {
pauseForFocusLoss = sp.getBoolean(PREF_PAUSE_PLAYBACK_FOR_FOCUS_LOSS, false);
}
} else if (key.equals(PREF_PAUSE_ON_HEADSET_DISCONNECT)) {
pauseOnHeadsetDisconnect = sp.getBoolean(PREF_PAUSE_ON_HEADSET_DISCONNECT, true);
}
}
public static void setPlaybackSpeed(String speed) {
@ -506,4 +521,9 @@ public class UserPreferences implements
instanceAvailable();
return instance.readEpisodeCacheSizeInternal(valueFromPrefs);
}
public static double getPlayedDurationAutoflattrThreshold() {
instanceAvailable();
return PLAYED_DURATION_AUTOFLATTR_THRESHOLD;
}
}

View File

@ -6,7 +6,7 @@ import android.content.Intent;
import android.util.Log;
import android.view.KeyEvent;
import de.danoeh.antennapod.AppConfig;
import de.danoeh.antennapod.service.PlaybackService;
import de.danoeh.antennapod.service.playback.PlaybackService;
/** Receives media button events. */
public class MediaButtonReceiver extends BroadcastReceiver {

View File

@ -6,7 +6,7 @@ import android.content.Context;
import android.content.Intent;
import android.util.Log;
import de.danoeh.antennapod.AppConfig;
import de.danoeh.antennapod.service.PlayerWidgetService;
import de.danoeh.antennapod.service.playback.PlayerWidgetService;
public class PlayerWidget extends AppWidgetProvider {
private static final String TAG = "PlayerWidget";

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,95 @@
package de.danoeh.antennapod.service.download;
import android.util.Log;
import de.danoeh.antennapod.AppConfig;
import org.apache.http.client.HttpClient;
import org.apache.http.client.params.HttpClientParams;
import org.apache.http.conn.ClientConnectionManager;
import org.apache.http.conn.params.ConnManagerPNames;
import org.apache.http.conn.scheme.PlainSocketFactory;
import org.apache.http.conn.scheme.Scheme;
import org.apache.http.conn.scheme.SchemeRegistry;
import org.apache.http.conn.ssl.SSLSocketFactory;
import org.apache.http.impl.client.AbstractHttpClient;
import org.apache.http.impl.client.DefaultHttpClient;
import org.apache.http.impl.conn.tsccm.ThreadSafeClientConnManager;
import org.apache.http.params.BasicHttpParams;
import org.apache.http.params.CoreProtocolPNames;
import org.apache.http.params.HttpConnectionParams;
import org.apache.http.params.HttpParams;
import java.util.concurrent.TimeUnit;
/**
* Provides access to a HttpClient singleton.
*/
public class AntennapodHttpClient {
private static final String TAG = "AntennapodHttpClient";
public static final long EXPIRED_CONN_TIMEOUT_SEC = 30;
public static final int MAX_REDIRECTS = 5;
public static final int CONNECTION_TIMEOUT = 30000;
public static final int SOCKET_TIMEOUT = 30000;
public static final int MAX_CONNECTIONS = 6;
private static volatile HttpClient httpClient = null;
/**
* Returns the HttpClient singleton.
*/
public static synchronized HttpClient getHttpClient() {
if (httpClient == null) {
if (AppConfig.DEBUG) Log.d(TAG, "Creating new instance of HTTP client");
HttpParams params = new BasicHttpParams();
params.setParameter(CoreProtocolPNames.USER_AGENT, AppConfig.USER_AGENT);
params.setIntParameter("http.protocol.max-redirects", MAX_REDIRECTS);
params.setBooleanParameter("http.protocol.reject-relative-redirect",
false);
HttpConnectionParams.setSoTimeout(params, SOCKET_TIMEOUT);
HttpConnectionParams.setConnectionTimeout(params, CONNECTION_TIMEOUT);
HttpClientParams.setRedirecting(params, true);
httpClient = new DefaultHttpClient(createClientConnectionManager(), params);
// Workaround for broken URLs in redirection
((AbstractHttpClient) httpClient)
.setRedirectHandler(new APRedirectHandler());
}
return httpClient;
}
/**
* Closes expired connections. This method should be called by the using class once has finished its work with
* the HTTP client.
*/
public static synchronized void cleanup() {
if (httpClient != null) {
httpClient.getConnectionManager().closeExpiredConnections();
httpClient.getConnectionManager().closeIdleConnections(EXPIRED_CONN_TIMEOUT_SEC, TimeUnit.SECONDS);
}
}
private static ClientConnectionManager createClientConnectionManager() {
HttpParams params = new BasicHttpParams();
params.setIntParameter(ConnManagerPNames.MAX_TOTAL_CONNECTIONS, MAX_CONNECTIONS);
return new ThreadSafeClientConnManager(params, prepareSchemeRegistry());
}
private static SchemeRegistry prepareSchemeRegistry() {
SchemeRegistry sr = new SchemeRegistry();
Scheme http = new Scheme("http",
PlainSocketFactory.getSocketFactory(), 80);
sr.register(http);
Scheme https = new Scheme("https",
SSLSocketFactory.getSocketFactory(), 443);
sr.register(https);
return sr;
}
}

View File

@ -11,6 +11,7 @@ import java.util.concurrent.atomic.AtomicInteger;
import javax.xml.parsers.ParserConfigurationException;
import android.media.MediaMetadataRetriever;
import de.danoeh.antennapod.storage.*;
import org.xml.sax.SAXException;
@ -25,7 +26,6 @@ import android.content.Intent;
import android.content.IntentFilter;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.media.MediaPlayer;
import android.os.AsyncTask;
import android.os.Binder;
import android.os.Handler;
@ -801,23 +801,21 @@ public class DownloadService extends Service {
media.setFile_url(request.getDestination());
// Get duration
MediaPlayer mediaplayer = null;
MediaMetadataRetriever mmr = null;
try {
mediaplayer = new MediaPlayer();
mediaplayer.setDataSource(media.getFile_url());
mediaplayer.prepare();
media.setDuration(mediaplayer.getDuration());
mmr = new MediaMetadataRetriever();
mmr.setDataSource(media.getFile_url());
String durationStr = mmr.extractMetadata(MediaMetadataRetriever.METADATA_KEY_DURATION);
media.setDuration(Integer.parseInt(durationStr));
if (AppConfig.DEBUG)
Log.d(TAG, "Duration of file is " + media.getDuration());
mediaplayer.reset();
} catch (IOException e) {
} catch (NumberFormatException e) {
e.printStackTrace();
} catch (RuntimeException e) {
// Thrown by MediaPlayer initialization on some devices
e.printStackTrace();
} finally {
if (mediaplayer != null) {
mediaplayer.release();
if (mmr != null) {
mmr.release();
}
}

View File

@ -1,5 +1,8 @@
package de.danoeh.antennapod.service.download;
import android.content.Context;
import android.net.wifi.WifiManager;
import de.danoeh.antennapod.PodcastApp;
import de.danoeh.antennapod.R;
import java.util.concurrent.Callable;
@ -26,7 +29,19 @@ public abstract class Downloader implements Callable<Downloader> {
protected abstract void download();
public final Downloader call() {
WifiManager wifiManager = (WifiManager) PodcastApp.getInstance().getSystemService(Context.WIFI_SERVICE);
WifiManager.WifiLock wifiLock = null;
if (wifiManager != null) {
wifiLock = wifiManager.createWifiLock(TAG);
wifiLock.acquire();
}
download();
if (wifiLock != null) {
wifiLock.release();
}
if (result == null) {
throw new IllegalStateException(
"Downloader hasn't created DownloadStatus object");

View File

@ -1,26 +1,5 @@
package de.danoeh.antennapod.service.download;
import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.net.HttpURLConnection;
import java.net.SocketTimeoutException;
import java.net.UnknownHostException;
import org.apache.commons.io.IOUtils;
import org.apache.http.Header;
import org.apache.http.HttpEntity;
import org.apache.http.HttpResponse;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.client.params.HttpClientParams;
import org.apache.http.impl.client.AbstractHttpClient;
import org.apache.http.impl.client.DefaultHttpClient;
import org.apache.http.params.HttpConnectionParams;
import org.apache.http.params.HttpParams;
import android.net.http.AndroidHttpClient;
import android.util.Log;
import de.danoeh.antennapod.AppConfig;
@ -28,44 +7,43 @@ import de.danoeh.antennapod.PodcastApp;
import de.danoeh.antennapod.R;
import de.danoeh.antennapod.util.DownloadError;
import de.danoeh.antennapod.util.StorageUtils;
import org.apache.commons.io.IOUtils;
import org.apache.http.Header;
import org.apache.http.HttpEntity;
import org.apache.http.HttpResponse;
import org.apache.http.client.HttpClient;
import org.apache.http.client.methods.HttpGet;
import java.io.*;
import java.net.*;
public class HttpDownloader extends Downloader {
private static final String TAG = "HttpDownloader";
private static final int MAX_REDIRECTS = 5;
private static final int BUFFER_SIZE = 8 * 1024;
private static final int CONNECTION_TIMEOUT = 30000;
private static final int SOCKET_TIMEOUT = 30000;
public HttpDownloader(DownloadRequest request) {
super(request);
}
private DefaultHttpClient createHttpClient() {
DefaultHttpClient httpClient = new DefaultHttpClient();
HttpParams params = httpClient.getParams();
params.setIntParameter("http.protocol.max-redirects", MAX_REDIRECTS);
params.setBooleanParameter("http.protocol.reject-relative-redirect",
false);
HttpConnectionParams.setSoTimeout(params, SOCKET_TIMEOUT);
HttpConnectionParams.setConnectionTimeout(params, CONNECTION_TIMEOUT);
HttpClientParams.setRedirecting(params, true);
// Workaround for broken URLs in redirection
((AbstractHttpClient) httpClient)
.setRedirectHandler(new APRedirectHandler());
return httpClient;
private URI getURIFromRequestUrl(String source) {
try {
URL url = new URL(source);
return new URI(url.getProtocol(), url.getUserInfo(), url.getHost(), url.getPort(), url.getPath(), url.getQuery(), url.getRef());
} catch (MalformedURLException e) {
throw new IllegalArgumentException(e);
} catch (URISyntaxException e) {
throw new IllegalArgumentException(e);
}
}
@Override
protected void download() {
DefaultHttpClient httpClient = null;
HttpClient httpClient = AntennapodHttpClient.getHttpClient();
BufferedOutputStream out = null;
InputStream connection = null;
try {
HttpGet httpGet = new HttpGet(request.getSource());
httpClient = createHttpClient();
HttpGet httpGet = new HttpGet(getURIFromRequestUrl(request.getSource()));
HttpResponse response = httpClient.execute(httpGet);
HttpEntity httpEntity = response.getEntity();
int responseCode = response.getStatusLine().getStatusCode();
@ -167,9 +145,7 @@ public class HttpDownloader extends Downloader {
onFail(DownloadError.ERROR_CONNECTION_ERROR, request.getSource());
} finally {
IOUtils.closeQuietly(out);
if (httpClient != null) {
httpClient.getConnectionManager().shutdown();
}
AntennapodHttpClient.cleanup();
}
}

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,923 @@
package de.danoeh.antennapod.service.playback;
import android.content.ComponentName;
import android.content.Context;
import android.media.AudioManager;
import android.media.RemoteControlClient;
import android.util.Log;
import android.util.Pair;
import android.view.SurfaceHolder;
import de.danoeh.antennapod.AppConfig;
import de.danoeh.antennapod.feed.Chapter;
import de.danoeh.antennapod.feed.MediaType;
import de.danoeh.antennapod.preferences.UserPreferences;
import de.danoeh.antennapod.receiver.MediaButtonReceiver;
import de.danoeh.antennapod.util.playback.AudioPlayer;
import de.danoeh.antennapod.util.playback.IPlayer;
import de.danoeh.antennapod.util.playback.Playable;
import de.danoeh.antennapod.util.playback.VideoPlayer;
import java.io.IOException;
import java.util.concurrent.LinkedBlockingDeque;
import java.util.concurrent.RejectedExecutionHandler;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.locks.ReentrantLock;
/**
* Manages the MediaPlayer object of the PlaybackService.
*/
public class PlaybackServiceMediaPlayer {
public static final String TAG = "PlaybackServiceMediaPlayer";
/**
* Return value of some PSMP methods if the method call failed.
*/
public static final int INVALID_TIME = -1;
private final AudioManager audioManager;
private volatile PlayerStatus playerStatus;
private volatile PlayerStatus statusBeforeSeeking;
private volatile IPlayer mediaPlayer;
private volatile Playable media;
private volatile boolean stream;
private volatile MediaType mediaType;
private volatile AtomicBoolean startWhenPrepared;
private volatile boolean pausedBecauseOfTransientAudiofocusLoss;
private volatile Pair<Integer, Integer> videoSize;
/**
* Some asynchronous calls might change the state of the MediaPlayer object. Therefore calls in other threads
* have to wait until these operations have finished.
*/
private final ReentrantLock playerLock;
private final PSMPCallback callback;
private final Context context;
private final ThreadPoolExecutor executor;
public PlaybackServiceMediaPlayer(Context context, PSMPCallback callback) {
if (context == null)
throw new IllegalArgumentException("context = null");
if (callback == null)
throw new IllegalArgumentException("callback = null");
this.context = context;
this.callback = callback;
this.audioManager = (AudioManager) context.getSystemService(Context.AUDIO_SERVICE);
this.playerLock = new ReentrantLock();
this.startWhenPrepared = new AtomicBoolean(false);
executor = new ThreadPoolExecutor(1, 1, 5, TimeUnit.MINUTES, new LinkedBlockingDeque<Runnable>(),
new RejectedExecutionHandler() {
@Override
public void rejectedExecution(Runnable r, ThreadPoolExecutor executor) {
if (AppConfig.DEBUG) Log.d(TAG, "Rejected execution of runnable");
}
});
mediaPlayer = null;
statusBeforeSeeking = null;
pausedBecauseOfTransientAudiofocusLoss = false;
mediaType = MediaType.UNKNOWN;
playerStatus = PlayerStatus.STOPPED;
videoSize = null;
}
/**
* Starts or prepares playback of the specified Playable object. If another Playable object is already being played, the currently playing
* episode will be stopped and replaced with the new Playable object. If the Playable object is already being played, the method will
* not do anything.
* Whether playback starts immediately depends on the given parameters. See below for more details.
* <p/>
* States:
* During execution of the method, the object will be in the INITIALIZING state. The end state depends on the given parameters.
* <p/>
* If 'prepareImmediately' is set to true, the method will go into PREPARING state and after that into PREPARED state. If
* 'startWhenPrepared' is set to true, the method will additionally go into PLAYING state.
* <p/>
* If an unexpected error occurs while loading the Playable's metadata or while setting the MediaPlayers data source, the object
* will enter the ERROR state.
* <p/>
* This method is executed on an internal executor service.
*
* @param playable The Playable object that is supposed to be played. This parameter must not be null.
* @param stream The type of playback. If false, the Playable object MUST provide access to a locally available file via
* getLocalMediaUrl. If true, the Playable object MUST provide access to a resource that can be streamed by
* the Android MediaPlayer via getStreamUrl.
* @param startWhenPrepared Sets the 'startWhenPrepared' flag. This flag determines whether playback will start immediately after the
* episode has been prepared for playback. Setting this flag to true does NOT mean that the episode will be prepared
* for playback immediately (see 'prepareImmediately' parameter for more details)
* @param prepareImmediately Set to true if the method should also prepare the episode for playback.
*/
public void playMediaObject(final Playable playable, final boolean stream, final boolean startWhenPrepared, final boolean prepareImmediately) {
if (playable == null)
throw new IllegalArgumentException("playable = null");
if (AppConfig.DEBUG) Log.d(TAG, "Play media object.");
executor.submit(new Runnable() {
@Override
public void run() {
playerLock.lock();
try {
playMediaObject(playable, false, stream, startWhenPrepared, prepareImmediately);
} catch (RuntimeException e) {
throw e;
} finally {
playerLock.unlock();
}
}
});
}
/**
* Internal implementation of playMediaObject. This method has an additional parameter that allows the caller to force a media player reset even if
* the given playable parameter is the same object as the currently playing media.
* <p/>
* This method requires the playerLock and is executed on the caller's thread.
*
* @see #playMediaObject(de.danoeh.antennapod.util.playback.Playable, boolean, boolean, boolean)
*/
private void playMediaObject(final Playable playable, final boolean forceReset, final boolean stream, final boolean startWhenPrepared, final boolean prepareImmediately) {
if (playable == null)
throw new IllegalArgumentException("playable = null");
if (!playerLock.isHeldByCurrentThread())
throw new IllegalStateException("method requires playerLock");
if (media != null) {
if (!forceReset && media.getIdentifier().equals(playable.getIdentifier())) {
// episode is already playing -> ignore method call
return;
} else {
// stop playback of this episode
if (playerStatus == PlayerStatus.PAUSED || playerStatus == PlayerStatus.PLAYING || playerStatus == PlayerStatus.PREPARED) {
mediaPlayer.stop();
}
setPlayerStatus(PlayerStatus.INDETERMINATE, null);
}
}
this.media = playable;
this.stream = stream;
this.mediaType = media.getMediaType();
this.videoSize = null;
createMediaPlayer();
PlaybackServiceMediaPlayer.this.startWhenPrepared.set(startWhenPrepared);
setPlayerStatus(PlayerStatus.INITIALIZING, media);
try {
media.loadMetadata();
if (stream) {
mediaPlayer.setDataSource(media.getStreamUrl());
} else {
mediaPlayer.setDataSource(media.getLocalMediaUrl());
}
setPlayerStatus(PlayerStatus.INITIALIZED, media);
if (mediaType == MediaType.VIDEO) {
VideoPlayer vp = (VideoPlayer) mediaPlayer;
// vp.setVideoScalingMode(MediaPlayer.VIDEO_SCALING_MODE_SCALE_TO_FIT);
}
if (prepareImmediately) {
setPlayerStatus(PlayerStatus.PREPARING, media);
mediaPlayer.prepare();
onPrepared(startWhenPrepared);
}
} catch (Playable.PlayableException e) {
e.printStackTrace();
setPlayerStatus(PlayerStatus.ERROR, null);
} catch (IOException e) {
e.printStackTrace();
setPlayerStatus(PlayerStatus.ERROR, null);
} catch (IllegalStateException e) {
e.printStackTrace();
setPlayerStatus(PlayerStatus.ERROR, null);
}
}
/**
* Resumes playback if the PSMP object is in PREPARED or PAUSED state. If the PSMP object is in an invalid state.
* nothing will happen.
* <p/>
* This method is executed on an internal executor service.
*/
public void resume() {
executor.submit(new Runnable() {
@Override
public void run() {
playerLock.lock();
resumeSync();
playerLock.unlock();
}
});
}
private void resumeSync() {
if (playerStatus == PlayerStatus.PAUSED || playerStatus == PlayerStatus.PREPARED) {
int focusGained = audioManager.requestAudioFocus(
audioFocusChangeListener, AudioManager.STREAM_MUSIC,
AudioManager.AUDIOFOCUS_GAIN);
if (focusGained == AudioManager.AUDIOFOCUS_REQUEST_GRANTED) {
setSpeed(Float.parseFloat(UserPreferences.getPlaybackSpeed()));
mediaPlayer.start();
if (playerStatus == PlayerStatus.PREPARED && media.getPosition() > 0) {
mediaPlayer.seekTo(media.getPosition());
}
setPlayerStatus(PlayerStatus.PLAYING, media);
pausedBecauseOfTransientAudiofocusLoss = false;
if (android.os.Build.VERSION.SDK_INT >= 14) {
RemoteControlClient remoteControlClient = callback.getRemoteControlClient();
if (remoteControlClient != null) {
audioManager
.registerRemoteControlClient(remoteControlClient);
}
}
audioManager
.registerMediaButtonEventReceiver(new ComponentName(context.getPackageName(),
MediaButtonReceiver.class.getName()));
media.onPlaybackStart();
} else {
if (AppConfig.DEBUG) Log.e(TAG, "Failed to request audio focus");
}
} else {
if (AppConfig.DEBUG)
Log.d(TAG, "Call to resume() was ignored because current state of PSMP object is " + playerStatus);
}
}
/**
* Saves the current position and pauses playback. Note that, if audiofocus
* is abandoned, the lockscreen controls will also disapear.
* <p/>
* This method is executed on an internal executor service.
*
* @param abandonFocus is true if the service should release audio focus
* @param reinit is true if service should reinit after pausing if the media
* file is being streamed
*/
public void pause(final boolean abandonFocus, final boolean reinit) {
executor.submit(new Runnable() {
@Override
public void run() {
playerLock.lock();
if (playerStatus == PlayerStatus.PLAYING) {
if (AppConfig.DEBUG)
Log.d(TAG, "Pausing playback.");
mediaPlayer.pause();
setPlayerStatus(PlayerStatus.PAUSED, media);
if (abandonFocus) {
audioManager.abandonAudioFocus(audioFocusChangeListener);
pausedBecauseOfTransientAudiofocusLoss = false;
}
if (stream && reinit) {
reinit();
}
} else {
if (AppConfig.DEBUG) Log.d(TAG, "Ignoring call to pause: Player is in " + playerStatus + " state");
}
playerLock.unlock();
}
});
}
/**
* Prepared media player for playback if the service is in the INITALIZED
* state.
* <p/>
* This method is executed on an internal executor service.
*/
public void prepare() {
executor.submit(new Runnable() {
@Override
public void run() {
playerLock.lock();
if (playerStatus == PlayerStatus.INITIALIZED) {
if (AppConfig.DEBUG)
Log.d(TAG, "Preparing media player");
setPlayerStatus(PlayerStatus.PREPARING, media);
try {
mediaPlayer.prepare();
onPrepared(startWhenPrepared.get());
} catch (IOException e) {
e.printStackTrace();
setPlayerStatus(PlayerStatus.ERROR, null);
}
}
playerLock.unlock();
}
});
}
/**
* Called after media player has been prepared. This method is executed on the caller's thread.
*/
void onPrepared(final boolean startWhenPrepared) {
playerLock.lock();
if (playerStatus != PlayerStatus.PREPARING) {
playerLock.unlock();
throw new IllegalStateException("Player is not in PREPARING state");
}
if (AppConfig.DEBUG)
Log.d(TAG, "Resource prepared");
if (mediaType == MediaType.VIDEO) {
VideoPlayer vp = (VideoPlayer) mediaPlayer;
videoSize = new Pair<Integer, Integer>(vp.getVideoWidth(), vp.getVideoHeight());
}
if (media.getPosition() > 0) {
mediaPlayer.seekTo(media.getPosition());
}
if (media.getDuration() == 0) {
if (AppConfig.DEBUG)
Log.d(TAG, "Setting duration of media");
media.setDuration(mediaPlayer.getDuration());
}
setPlayerStatus(PlayerStatus.PREPARED, media);
if (startWhenPrepared) {
resumeSync();
}
playerLock.unlock();
}
/**
* Resets the media player and moves it into INITIALIZED state.
* <p/>
* This method is executed on an internal executor service.
*/
public void reinit() {
executor.submit(new Runnable() {
@Override
public void run() {
playerLock.lock();
if (media != null) {
playMediaObject(media, true, stream, startWhenPrepared.get(), false);
} else if (mediaPlayer != null) {
mediaPlayer.reset();
} else {
if (AppConfig.DEBUG) Log.d(TAG, "Call to reinit was ignored: media and mediaPlayer were null");
}
playerLock.unlock();
}
});
}
/**
* Seeks to the specified position. If the PSMP object is in an invalid state, this method will do nothing.
* Invalid time values (< 0) will be ignored.
* <p/>
* This method is executed on the caller's thread.
*/
private void seekToSync(int t) {
if (t < 0) {
if (AppConfig.DEBUG) Log.d(TAG, "Received invalid value for t");
return;
}
playerLock.lock();
if (playerStatus == PlayerStatus.PLAYING
|| playerStatus == PlayerStatus.PAUSED
|| playerStatus == PlayerStatus.PREPARED) {
if (stream) {
// statusBeforeSeeking = playerStatus;
// setPlayerStatus(PlayerStatus.SEEKING, media);
}
mediaPlayer.seekTo(t);
} else if (playerStatus == PlayerStatus.INITIALIZED) {
media.setPosition(t);
startWhenPrepared.set(true);
prepare();
}
playerLock.unlock();
}
/**
* Seeks to the specified position. If the PSMP object is in an invalid state, this method will do nothing.
* Invalid time values (< 0) will be ignored.
* <p/>
* This method is executed on an internal executor service.
*/
public void seekTo(final int t) {
executor.submit(new Runnable() {
@Override
public void run() {
seekToSync(t);
}
});
}
/**
* Seek a specific position from the current position
*
* @param d offset from current position (positive or negative)
*/
public void seekDelta(final int d) {
executor.submit(new Runnable() {
@Override
public void run() {
playerLock.lock();
int currentPosition = getPosition();
if (currentPosition != INVALID_TIME) {
seekToSync(currentPosition + d);
} else {
Log.e(TAG, "getPosition() returned INVALID_TIME in seekDelta");
}
playerLock.unlock();
}
});
}
/**
* Seek to the start of the specified chapter.
*/
public void seekToChapter(Chapter c) {
if (c == null)
throw new IllegalArgumentException("c = null");
seekTo((int) c.getStart());
}
/**
* Returns the duration of the current media object or INVALID_TIME if the duration could not be retrieved.
*/
public int getDuration() {
if (!playerLock.tryLock()) {
return INVALID_TIME;
}
int retVal = INVALID_TIME;
if (playerStatus == PlayerStatus.PLAYING
|| playerStatus == PlayerStatus.PAUSED
|| playerStatus == PlayerStatus.PREPARED) {
retVal = mediaPlayer.getDuration();
} else if (media != null && media.getDuration() > 0) {
retVal = media.getDuration();
}
playerLock.unlock();
return retVal;
}
/**
* Returns the position of the current media object or INVALID_TIME if the position could not be retrieved.
*/
public int getPosition() {
if (!playerLock.tryLock()) {
return INVALID_TIME;
}
int retVal = INVALID_TIME;
if (playerStatus == PlayerStatus.PLAYING
|| playerStatus == PlayerStatus.PAUSED
|| playerStatus == PlayerStatus.PREPARED) {
retVal = mediaPlayer.getCurrentPosition();
} else if (media != null && media.getPosition() > 0) {
retVal = media.getPosition();
}
playerLock.unlock();
return retVal;
}
public boolean isStartWhenPrepared() {
return startWhenPrepared.get();
}
public void setStartWhenPrepared(boolean startWhenPrepared) {
this.startWhenPrepared.set(startWhenPrepared);
}
/**
* Returns true if the playback speed can be adjusted. This method can also return false if the PSMP object's
* internal MediaPlayer cannot be accessed at the moment.
*/
public boolean canSetSpeed() {
if (!playerLock.tryLock()) {
return false;
}
boolean retVal = false;
if (mediaPlayer != null && media != null && media.getMediaType() == MediaType.AUDIO) {
retVal = (mediaPlayer).canSetSpeed();
}
playerLock.unlock();
return retVal;
}
/**
* Sets the playback speed.
* This method is executed on the caller's thread.
*/
private void setSpeedSync(float speed) {
playerLock.lock();
if (media != null && media.getMediaType() == MediaType.AUDIO) {
if (mediaPlayer.canSetSpeed()) {
mediaPlayer.setPlaybackSpeed((float) speed);
if (AppConfig.DEBUG)
Log.d(TAG, "Playback speed was set to " + speed);
callback.playbackSpeedChanged(speed);
}
}
playerLock.unlock();
}
/**
* Sets the playback speed.
* This method is executed on an internal executor service.
*/
public void setSpeed(final float speed) {
executor.submit(new Runnable() {
@Override
public void run() {
setSpeedSync(speed);
}
});
}
/**
* Returns the current playback speed. If the playback speed could not be retrieved, 1 is returned.
*/
public float getPlaybackSpeed() {
if (!playerLock.tryLock()) {
return 1;
}
float retVal = 1;
if ((playerStatus == PlayerStatus.PLAYING
|| playerStatus == PlayerStatus.PAUSED
|| playerStatus == PlayerStatus.PREPARED) && mediaPlayer.canSetSpeed()) {
retVal = mediaPlayer.getCurrentSpeedMultiplier();
}
playerLock.unlock();
return retVal;
}
public MediaType getCurrentMediaType() {
return mediaType;
}
public boolean isStreaming() {
return stream;
}
/**
* Releases internally used resources. This method should only be called when the object is not used anymore.
*/
public void shutdown() {
executor.shutdown();
if (mediaPlayer != null) {
mediaPlayer.release();
}
}
public void setVideoSurface(final SurfaceHolder surface) {
executor.submit(new Runnable() {
@Override
public void run() {
playerLock.lock();
if (mediaPlayer != null) {
mediaPlayer.setDisplay(surface);
}
playerLock.unlock();
}
});
}
public void resetVideoSurface() {
executor.submit(new Runnable() {
@Override
public void run() {
playerLock.lock();
if (AppConfig.DEBUG)
Log.d(TAG, "Resetting video surface");
mediaPlayer.setDisplay(null);
reinit();
playerLock.unlock();
}
});
}
/**
* Return width and height of the currently playing video as a pair.
*
* @return Width and height as a Pair or null if the video size could not be determined. The method might still
* return an invalid non-null value if the getVideoWidth() and getVideoHeight() methods of the media player return
* invalid values.
*/
public Pair<Integer, Integer> getVideoSize() {
if (!playerLock.tryLock()) {
// use cached value if lock can't be aquired
return videoSize;
}
Pair<Integer, Integer> res;
if (mediaPlayer == null || playerStatus == PlayerStatus.ERROR || mediaType != MediaType.VIDEO) {
res = null;
} else {
VideoPlayer vp = (VideoPlayer) mediaPlayer;
videoSize = new Pair<Integer, Integer>(vp.getVideoWidth(), vp.getVideoHeight());
res = videoSize;
}
playerLock.unlock();
return res;
}
/**
* Returns a PSMInfo object that contains information about the current state of the PSMP object.
*
* @return The PSMPInfo object.
*/
public synchronized PSMPInfo getPSMPInfo() {
return new PSMPInfo(playerStatus, media);
}
/**
* Sets the player status of the PSMP object. PlayerStatus and media attributes have to be set at the same time
* so that getPSMPInfo can't return an invalid state (e.g. status is PLAYING, but media is null).
* <p/>
* This method will notify the callback about the change of the player status (even if the new status is the same
* as the old one).
*
* @param newStatus The new PlayerStatus. This must not be null.
* @param newMedia The new playable object of the PSMP object. This can be null.
*/
private synchronized void setPlayerStatus(PlayerStatus newStatus, Playable newMedia) {
if (newStatus == null)
throw new IllegalArgumentException("newStatus = null");
if (AppConfig.DEBUG) Log.d(TAG, "Setting player status to " + newStatus);
this.playerStatus = newStatus;
this.media = newMedia;
callback.statusChanged(new PSMPInfo(playerStatus, media));
}
private IPlayer createMediaPlayer() {
if (mediaPlayer != null) {
mediaPlayer.release();
}
if (media == null || media.getMediaType() == MediaType.VIDEO) {
mediaPlayer = new VideoPlayer();
} else {
mediaPlayer = new AudioPlayer(context);
}
mediaPlayer.setAudioStreamType(AudioManager.STREAM_MUSIC);
return setMediaPlayerListeners(mediaPlayer);
}
private final AudioManager.OnAudioFocusChangeListener audioFocusChangeListener = new AudioManager.OnAudioFocusChangeListener() {
@Override
public void onAudioFocusChange(final int focusChange) {
executor.submit(new Runnable() {
@Override
public void run() {
playerLock.lock();
switch (focusChange) {
case AudioManager.AUDIOFOCUS_LOSS:
if (AppConfig.DEBUG)
Log.d(TAG, "Lost audio focus");
pause(true, false);
callback.shouldStop();
break;
case AudioManager.AUDIOFOCUS_GAIN:
if (AppConfig.DEBUG)
Log.d(TAG, "Gained audio focus");
if (pausedBecauseOfTransientAudiofocusLoss) // we paused => play now
resume();
else // we ducked => raise audio level back
audioManager.adjustStreamVolume(AudioManager.STREAM_MUSIC,
AudioManager.ADJUST_RAISE, 0);
break;
case AudioManager.AUDIOFOCUS_LOSS_TRANSIENT_CAN_DUCK:
if (playerStatus == PlayerStatus.PLAYING) {
if (!UserPreferences.shouldPauseForFocusLoss()) {
if (AppConfig.DEBUG)
Log.d(TAG, "Lost audio focus temporarily. Ducking...");
audioManager.adjustStreamVolume(AudioManager.STREAM_MUSIC,
AudioManager.ADJUST_LOWER, 0);
pausedBecauseOfTransientAudiofocusLoss = false;
} else {
if (AppConfig.DEBUG)
Log.d(TAG, "Lost audio focus temporarily. Could duck, but won't, pausing...");
pause(false, false);
pausedBecauseOfTransientAudiofocusLoss = true;
}
}
break;
case AudioManager.AUDIOFOCUS_LOSS_TRANSIENT:
if (playerStatus == PlayerStatus.PLAYING) {
if (AppConfig.DEBUG)
Log.d(TAG, "Lost audio focus temporarily. Pausing...");
pause(false, false);
pausedBecauseOfTransientAudiofocusLoss = true;
}
}
playerLock.unlock();
}
});
}
};
public void endPlayback() {
executor.submit(new Runnable() {
@Override
public void run() {
playerLock.lock();
if (playerStatus != PlayerStatus.INDETERMINATE) {
setPlayerStatus(PlayerStatus.INDETERMINATE, media);
}
if (mediaPlayer != null) {
mediaPlayer.reset();
}
callback.endPlayback(true);
playerLock.unlock();
}
});
}
/**
* Holds information about a PSMP object.
*/
public class PSMPInfo {
public PlayerStatus playerStatus;
public Playable playable;
public PSMPInfo(PlayerStatus playerStatus, Playable playable) {
this.playerStatus = playerStatus;
this.playable = playable;
}
}
public static interface PSMPCallback {
public void statusChanged(PSMPInfo newInfo);
public void shouldStop();
public void playbackSpeedChanged(float s);
public void onBufferingUpdate(int percent);
public boolean onMediaPlayerInfo(int code);
public boolean onMediaPlayerError(Object inObj, int what, int extra);
public boolean endPlayback(boolean playNextEpisode);
public RemoteControlClient getRemoteControlClient();
}
private IPlayer setMediaPlayerListeners(IPlayer mp) {
if (mp != null && media != null) {
if (media.getMediaType() == MediaType.AUDIO) {
((AudioPlayer) mp)
.setOnCompletionListener(audioCompletionListener);
((AudioPlayer) mp)
.setOnSeekCompleteListener(audioSeekCompleteListener);
((AudioPlayer) mp).setOnErrorListener(audioErrorListener);
((AudioPlayer) mp)
.setOnBufferingUpdateListener(audioBufferingUpdateListener);
((AudioPlayer) mp).setOnInfoListener(audioInfoListener);
} else {
((VideoPlayer) mp)
.setOnCompletionListener(videoCompletionListener);
((VideoPlayer) mp)
.setOnSeekCompleteListener(videoSeekCompleteListener);
((VideoPlayer) mp).setOnErrorListener(videoErrorListener);
((VideoPlayer) mp)
.setOnBufferingUpdateListener(videoBufferingUpdateListener);
((VideoPlayer) mp).setOnInfoListener(videoInfoListener);
}
}
return mp;
}
private final com.aocate.media.MediaPlayer.OnCompletionListener audioCompletionListener = new com.aocate.media.MediaPlayer.OnCompletionListener() {
@Override
public void onCompletion(com.aocate.media.MediaPlayer mp) {
genericOnCompletion();
}
};
private final android.media.MediaPlayer.OnCompletionListener videoCompletionListener = new android.media.MediaPlayer.OnCompletionListener() {
@Override
public void onCompletion(android.media.MediaPlayer mp) {
genericOnCompletion();
}
};
private void genericOnCompletion() {
endPlayback();
}
private final com.aocate.media.MediaPlayer.OnBufferingUpdateListener audioBufferingUpdateListener = new com.aocate.media.MediaPlayer.OnBufferingUpdateListener() {
@Override
public void onBufferingUpdate(com.aocate.media.MediaPlayer mp,
int percent) {
genericOnBufferingUpdate(percent);
}
};
private final android.media.MediaPlayer.OnBufferingUpdateListener videoBufferingUpdateListener = new android.media.MediaPlayer.OnBufferingUpdateListener() {
@Override
public void onBufferingUpdate(android.media.MediaPlayer mp, int percent) {
genericOnBufferingUpdate(percent);
}
};
private void genericOnBufferingUpdate(int percent) {
callback.onBufferingUpdate(percent);
}
private final com.aocate.media.MediaPlayer.OnInfoListener audioInfoListener = new com.aocate.media.MediaPlayer.OnInfoListener() {
@Override
public boolean onInfo(com.aocate.media.MediaPlayer mp, int what,
int extra) {
return genericInfoListener(what);
}
};
private final android.media.MediaPlayer.OnInfoListener videoInfoListener = new android.media.MediaPlayer.OnInfoListener() {
@Override
public boolean onInfo(android.media.MediaPlayer mp, int what, int extra) {
return genericInfoListener(what);
}
};
private boolean genericInfoListener(int what) {
return callback.onMediaPlayerInfo(what);
}
private final com.aocate.media.MediaPlayer.OnErrorListener audioErrorListener = new com.aocate.media.MediaPlayer.OnErrorListener() {
@Override
public boolean onError(com.aocate.media.MediaPlayer mp, int what,
int extra) {
return genericOnError(mp, what, extra);
}
};
private final android.media.MediaPlayer.OnErrorListener videoErrorListener = new android.media.MediaPlayer.OnErrorListener() {
@Override
public boolean onError(android.media.MediaPlayer mp, int what, int extra) {
return genericOnError(mp, what, extra);
}
};
private boolean genericOnError(Object inObj, int what, int extra) {
return callback.onMediaPlayerError(inObj, what, extra);
}
private final com.aocate.media.MediaPlayer.OnSeekCompleteListener audioSeekCompleteListener = new com.aocate.media.MediaPlayer.OnSeekCompleteListener() {
@Override
public void onSeekComplete(com.aocate.media.MediaPlayer mp) {
genericSeekCompleteListener();
}
};
private final android.media.MediaPlayer.OnSeekCompleteListener videoSeekCompleteListener = new android.media.MediaPlayer.OnSeekCompleteListener() {
@Override
public void onSeekComplete(android.media.MediaPlayer mp) {
genericSeekCompleteListener();
}
};
private final void genericSeekCompleteListener() {
executor.submit(new Runnable() {
@Override
public void run() {
playerLock.lock();
if (playerStatus == PlayerStatus.SEEKING) {
setPlayerStatus(statusBeforeSeeking, media);
}
playerLock.unlock();
}
});
}
}

View File

@ -0,0 +1,385 @@
package de.danoeh.antennapod.service.playback;
import android.content.Context;
import android.util.Log;
import de.danoeh.antennapod.AppConfig;
import de.danoeh.antennapod.feed.EventDistributor;
import de.danoeh.antennapod.feed.FeedItem;
import de.danoeh.antennapod.storage.DBReader;
import de.danoeh.antennapod.util.playback.Playable;
import java.util.List;
import java.util.concurrent.*;
/**
* Manages the background tasks of PlaybackSerivce, i.e.
* the sleep timer, the position saver, the widget updater and
* the queue loader.
* <p/>
* The PlaybackServiceTaskManager(PSTM) uses a callback object (PSTMCallback)
* to notify the PlaybackService about updates from the running tasks.
*/
public class PlaybackServiceTaskManager {
private static final String TAG = "PlaybackServiceTaskManager";
/**
* Update interval of position saver in milliseconds.
*/
public static final int POSITION_SAVER_WAITING_INTERVAL = 5000;
/**
* Notification interval of widget updater in milliseconds.
*/
public static final int WIDGET_UPDATER_NOTIFICATION_INTERVAL = 1500;
private static final int SCHED_EX_POOL_SIZE = 2;
private final ScheduledThreadPoolExecutor schedExecutor;
private ScheduledFuture positionSaverFuture;
private ScheduledFuture widgetUpdaterFuture;
private ScheduledFuture sleepTimerFuture;
private volatile Future<List<FeedItem>> queueFuture;
private volatile Future chapterLoaderFuture;
private SleepTimer sleepTimer;
private final Context context;
private final PSTMCallback callback;
/**
* Sets up a new PSTM. This method will also start the queue loader task.
*
* @param context
* @param callback A PSTMCallback object for notifying the user about updates. Must not be null.
*/
public PlaybackServiceTaskManager(Context context, PSTMCallback callback) {
if (context == null)
throw new IllegalArgumentException("context must not be null");
if (callback == null)
throw new IllegalArgumentException("callback must not be null");
this.context = context;
this.callback = callback;
schedExecutor = new ScheduledThreadPoolExecutor(SCHED_EX_POOL_SIZE, new ThreadFactory() {
@Override
public Thread newThread(Runnable r) {
Thread t = new Thread(r);
t.setPriority(Thread.MIN_PRIORITY);
return t;
}
});
loadQueue();
EventDistributor.getInstance().register(eventDistributorListener);
}
private final EventDistributor.EventListener eventDistributorListener = new EventDistributor.EventListener() {
@Override
public void update(EventDistributor eventDistributor, Integer arg) {
if ((EventDistributor.QUEUE_UPDATE & arg) != 0) {
cancelQueueLoader();
loadQueue();
}
}
};
private synchronized boolean isQueueLoaderActive() {
return queueFuture != null && !queueFuture.isDone();
}
private synchronized void cancelQueueLoader() {
if (isQueueLoaderActive()) {
queueFuture.cancel(true);
}
}
private synchronized void loadQueue() {
if (!isQueueLoaderActive()) {
queueFuture = schedExecutor.submit(new Callable<List<FeedItem>>() {
@Override
public List<FeedItem> call() throws Exception {
return DBReader.getQueue(context);
}
});
}
}
/**
* Returns the queue if it is already loaded or null if it hasn't been loaded yet.
* In order to wait until the queue has been loaded, use getQueue()
*/
public synchronized List<FeedItem> getQueueIfLoaded() {
if (queueFuture.isDone()) {
try {
return queueFuture.get();
} catch (InterruptedException e) {
e.printStackTrace();
} catch (ExecutionException e) {
e.printStackTrace();
}
}
return null;
}
/**
* Returns the queue or waits until the PSTM has loaded the queue from the database.
*/
public synchronized List<FeedItem> getQueue() throws InterruptedException {
try {
return queueFuture.get();
} catch (ExecutionException e) {
throw new IllegalArgumentException(e);
}
}
/**
* Starts the position saver task. If the position saver is already active, nothing will happen.
*/
public synchronized void startPositionSaver() {
if (!isPositionSaverActive()) {
Runnable positionSaver = new Runnable() {
@Override
public void run() {
callback.positionSaverTick();
}
};
positionSaverFuture = schedExecutor.scheduleWithFixedDelay(positionSaver, POSITION_SAVER_WAITING_INTERVAL,
POSITION_SAVER_WAITING_INTERVAL, TimeUnit.MILLISECONDS);
if (AppConfig.DEBUG) Log.d(TAG, "Started PositionSaver");
} else {
if (AppConfig.DEBUG) Log.d(TAG, "Call to startPositionSaver was ignored.");
}
}
/**
* Returns true if the position saver is currently running.
*/
public synchronized boolean isPositionSaverActive() {
return positionSaverFuture != null && !positionSaverFuture.isCancelled() && !positionSaverFuture.isDone();
}
/**
* Cancels the position saver. If the position saver is not running, nothing will happen.
*/
public synchronized void cancelPositionSaver() {
if (isPositionSaverActive()) {
positionSaverFuture.cancel(false);
if (AppConfig.DEBUG) Log.d(TAG, "Cancelled PositionSaver");
}
}
/**
* Starts the widget updater task. If the widget updater is already active, nothing will happen.
*/
public synchronized void startWidgetUpdater() {
if (!isWidgetUpdaterActive()) {
Runnable widgetUpdater = new Runnable() {
@Override
public void run() {
callback.onWidgetUpdaterTick();
}
};
widgetUpdaterFuture = schedExecutor.scheduleWithFixedDelay(widgetUpdater, WIDGET_UPDATER_NOTIFICATION_INTERVAL,
WIDGET_UPDATER_NOTIFICATION_INTERVAL, TimeUnit.MILLISECONDS);
if (AppConfig.DEBUG) Log.d(TAG, "Started WidgetUpdater");
} else {
if (AppConfig.DEBUG) Log.d(TAG, "Call to startWidgetUpdater was ignored.");
}
}
/**
* Starts a new sleep timer with the given waiting time. If another sleep timer is already active, it will be
* cancelled first.
* After waitingTime has elapsed, onSleepTimerExpired() will be called.
*
* @throws java.lang.IllegalArgumentException if waitingTime <= 0
*/
public synchronized void setSleepTimer(long waitingTime) {
if (waitingTime <= 0)
throw new IllegalArgumentException("waitingTime <= 0");
if (AppConfig.DEBUG)
Log.d(TAG, "Setting sleep timer to " + Long.toString(waitingTime)
+ " milliseconds");
if (isSleepTimerActive()) {
sleepTimerFuture.cancel(true);
}
sleepTimer = new SleepTimer(waitingTime);
sleepTimerFuture = schedExecutor.schedule(sleepTimer, 0, TimeUnit.MILLISECONDS);
}
/**
* Returns true if the sleep timer is currently active.
*/
public synchronized boolean isSleepTimerActive() {
return sleepTimer != null && sleepTimerFuture != null && !sleepTimerFuture.isCancelled() && !sleepTimerFuture.isDone() && sleepTimer.isWaiting;
}
/**
* Disables the sleep timer. If the sleep timer is not active, nothing will happen.
*/
public synchronized void disableSleepTimer() {
if (isSleepTimerActive()) {
if (AppConfig.DEBUG)
Log.d(TAG, "Disabling sleep timer");
sleepTimerFuture.cancel(true);
}
}
/**
* Returns the current sleep timer time or 0 if the sleep timer is not active.
*/
public synchronized long getSleepTimerTimeLeft() {
if (isSleepTimerActive()) {
return sleepTimer.getWaitingTime();
} else {
return 0;
}
}
/**
* Returns true if the widget updater is currently running.
*/
public synchronized boolean isWidgetUpdaterActive() {
return widgetUpdaterFuture != null && !widgetUpdaterFuture.isCancelled() && !widgetUpdaterFuture.isDone();
}
/**
* Cancels the widget updater. If the widget updater is not running, nothing will happen.
*/
public synchronized void cancelWidgetUpdater() {
if (isWidgetUpdaterActive()) {
widgetUpdaterFuture.cancel(false);
if (AppConfig.DEBUG) Log.d(TAG, "Cancelled WidgetUpdater");
}
}
private synchronized void cancelChapterLoader() {
if (isChapterLoaderActive()) {
chapterLoaderFuture.cancel(true);
}
}
private synchronized boolean isChapterLoaderActive() {
return chapterLoaderFuture != null && !chapterLoaderFuture.isDone();
}
/**
* Starts a new thread that loads the chapter marks from a playable object. If another chapter loader is already active,
* it will be cancelled first.
* On completion, the callback's onChapterLoaded method will be called.
*/
public synchronized void startChapterLoader(final Playable media) {
if (media == null)
throw new IllegalArgumentException("media = null");
if (isChapterLoaderActive()) {
cancelChapterLoader();
}
Runnable chapterLoader = new Runnable() {
@Override
public void run() {
if (AppConfig.DEBUG)
Log.d(TAG, "Chapter loader started");
if (media.getChapters() == null) {
media.loadChapterMarks();
if (!Thread.currentThread().isInterrupted() && media.getChapters() != null) {
callback.onChapterLoaded(media);
}
}
if (AppConfig.DEBUG)
Log.d(TAG, "Chapter loader stopped");
}
};
chapterLoaderFuture = schedExecutor.submit(chapterLoader);
}
/**
* Cancels all tasks. The PSTM will be in the initial state after execution of this method.
*/
public synchronized void cancelAllTasks() {
cancelPositionSaver();
cancelWidgetUpdater();
disableSleepTimer();
cancelQueueLoader();
cancelChapterLoader();
}
/**
* Cancels all tasks and shuts down the internal executor service of the PSTM. The object should not be used after
* execution of this method.
*/
public synchronized void shutdown() {
EventDistributor.getInstance().unregister(eventDistributorListener);
cancelAllTasks();
schedExecutor.shutdown();
}
/**
* Sleeps for a given time and then pauses playback.
*/
private class SleepTimer implements Runnable {
private static final String TAG = "SleepTimer";
private static final long UPDATE_INTERVALL = 1000L;
private volatile long waitingTime;
private volatile boolean isWaiting;
public SleepTimer(long waitingTime) {
super();
this.waitingTime = waitingTime;
isWaiting = true;
}
@Override
public void run() {
if (AppConfig.DEBUG)
Log.d(TAG, "Starting");
while (waitingTime > 0) {
try {
Thread.sleep(UPDATE_INTERVALL);
waitingTime -= UPDATE_INTERVALL;
if (waitingTime <= 0) {
if (AppConfig.DEBUG)
Log.d(TAG, "Waiting completed");
postExecute();
if (!Thread.currentThread().isInterrupted()) {
callback.onSleepTimerExpired();
}
}
} catch (InterruptedException e) {
Log.d(TAG, "Thread was interrupted while waiting");
break;
}
}
postExecute();
}
protected void postExecute() {
isWaiting = false;
}
public long getWaitingTime() {
return waitingTime;
}
public boolean isWaiting() {
return isWaiting;
}
}
public static interface PSTMCallback {
void positionSaverTick();
void onSleepTimerExpired();
void onWidgetUpdaterTick();
void onChapterLoaded(Playable media);
}
}

View File

@ -1,14 +1,14 @@
package de.danoeh.antennapod.service;
package de.danoeh.antennapod.service.playback;
public enum PlayerStatus {
INDETERMINATE, // player is currently changing its state, listeners should wait until the player has left this state.
ERROR,
PREPARING,
PAUSED,
PLAYING,
STOPPED,
PREPARED,
SEEKING,
AWAITING_VIDEO_SURFACE, // player has been initialized and the media type to be played is a video.
SEEKING,
INITIALIZING, // playback service is loading the Playable's metadata
INITIALIZED // playback service was started, data source of media player was set.
}

View File

@ -1,4 +1,4 @@
package de.danoeh.antennapod.service;
package de.danoeh.antennapod.service.playback;
import android.app.PendingIntent;
import android.app.Service;
@ -6,6 +6,7 @@ import android.appwidget.AppWidgetManager;
import android.content.ComponentName;
import android.content.Intent;
import android.content.ServiceConnection;
import android.os.Build;
import android.os.IBinder;
import android.util.Log;
import android.view.KeyEvent;
@ -72,9 +73,11 @@ public class PlayerWidgetService extends Service {
}
private void updateViews() {
if (playbackService == null) {
return;
}
isUpdating = true;
if (AppConfig.DEBUG)
Log.d(TAG, "Updating widget views");
ComponentName playerWidget = new ComponentName(this, PlayerWidget.class);
AppWidgetManager manager = AppWidgetManager.getInstance(this);
RemoteViews views = new RemoteViews(getPackageName(),
@ -83,8 +86,8 @@ public class PlayerWidgetService extends Service {
PlaybackService.getPlayerActivityIntent(this), 0);
views.setOnClickPendingIntent(R.id.layout_left, startMediaplayer);
if (playbackService != null && playbackService.getMedia() != null) {
Playable media = playbackService.getMedia();
final Playable media = playbackService.getPlayable();
if (playbackService != null && media != null) {
PlayerStatus status = playbackService.getStatus();
views.setTextViewText(R.id.txtvTitle, media.getEpisodeTitle());
@ -95,14 +98,18 @@ public class PlayerWidgetService extends Service {
views.setTextViewText(R.id.txtvProgress, progressString);
}
views.setImageViewResource(R.id.butPlay, R.drawable.av_pause_dark);
if (Build.VERSION.SDK_INT >= 15) {
views.setContentDescription(R.id.butPlay, getString(R.string.pause_label));
}
} else {
views.setImageViewResource(R.id.butPlay, R.drawable.av_play_dark);
if (Build.VERSION.SDK_INT >= 15) {
views.setContentDescription(R.id.butPlay, getString(R.string.play_label));
}
}
views.setOnClickPendingIntent(R.id.butPlay,
createMediaButtonIntent());
} else {
if (AppConfig.DEBUG)
Log.d(TAG, "No media playing. Displaying defaultt views");
views.setViewVisibility(R.id.txtvProgress, View.INVISIBLE);
views.setTextViewText(R.id.txtvTitle,
this.getString(R.string.no_media_playing_label));
@ -126,8 +133,8 @@ public class PlayerWidgetService extends Service {
}
private String getProgressString(PlaybackService ps) {
int position = ps.getCurrentPositionSafe();
int duration = ps.getDurationSafe();
int position = ps.getCurrentPosition();
int duration = ps.getDuration();
if (position != PlaybackService.INVALID_TIME
&& duration != PlaybackService.INVALID_TIME) {
return Converter.getDurationStringLong(position) + " / "

View File

@ -1,20 +1,23 @@
package de.danoeh.antennapod.storage;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Date;
import java.util.List;
import android.content.Context;
import android.database.Cursor;
import android.database.SQLException;
import android.util.Log;
import de.danoeh.antennapod.AppConfig;
import de.danoeh.antennapod.feed.*;
import de.danoeh.antennapod.service.download.*;
import de.danoeh.antennapod.service.download.DownloadStatus;
import de.danoeh.antennapod.util.DownloadError;
import de.danoeh.antennapod.util.comparator.DownloadStatusComparator;
import de.danoeh.antennapod.util.comparator.FeedItemPubdateComparator;
import de.danoeh.antennapod.util.flattr.FlattrStatus;
import de.danoeh.antennapod.util.flattr.FlattrThing;
import de.danoeh.antennapod.util.comparator.PlaybackCompletionDateComparator;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Date;
import java.util.List;
/**
* Provides methods for reading data from the AntennaPod database.
@ -123,6 +126,7 @@ public final class DBReader {
* Takes a list of FeedItems and loads their corresponding Feed-objects from the database.
* The feedID-attribute of a FeedItem must be set to the ID of its feed or the method will
* not find the correct feed of an item.
*
* @param context A context that is used for opening a database connection.
* @param items The FeedItems whose Feed-objects should be loaded.
*/
@ -210,7 +214,9 @@ public final class DBReader {
.getInt(PodDBAdapter.IDX_FI_SMALL_READ) > 0));
item.setItemIdentifier(itemlistCursor
.getString(PodDBAdapter.IDX_FI_SMALL_ITEM_IDENTIFIER));
item.setFlattrStatus(new FlattrStatus(itemlistCursor
.getLong(PodDBAdapter.IDX_FI_SMALL_FLATTR_STATUS)));
// extract chapters
boolean hasSimpleChapters = itemlistCursor
.getInt(PodDBAdapter.IDX_FI_SMALL_HAS_CHAPTERS) > 0;
@ -301,7 +307,8 @@ public final class DBReader {
cursor.getString(PodDBAdapter.KEY_FILE_URL_INDEX),
cursor.getString(PodDBAdapter.KEY_DOWNLOAD_URL_INDEX),
cursor.getInt(PodDBAdapter.KEY_DOWNLOADED_INDEX) > 0,
playbackCompletionDate);
playbackCompletionDate,
cursor.getInt(PodDBAdapter.KEY_PLAYED_DURATION_INDEX));
}
private static Feed extractFeedFromCursorRow(PodDBAdapter adapter,
@ -329,7 +336,8 @@ public final class DBReader {
image,
cursor.getString(PodDBAdapter.IDX_FEED_SEL_STD_FILE_URL),
cursor.getString(PodDBAdapter.IDX_FEED_SEL_STD_DOWNLOAD_URL),
cursor.getInt(PodDBAdapter.IDX_FEED_SEL_STD_DOWNLOADED) > 0);
cursor.getInt(PodDBAdapter.IDX_FEED_SEL_STD_DOWNLOADED) > 0,
new FlattrStatus(cursor.getLong(PodDBAdapter.IDX_FEED_SEL_STD_FLATTR_STATUS)));
if (image != null) {
image.setFeed(feed);
@ -515,8 +523,9 @@ public final class DBReader {
List<FeedItem> items = extractItemlistFromCursor(adapter, itemCursor);
loadFeedDataOfFeedItemlist(context, items);
itemCursor.close();
adapter.close();
Collections.sort(items, new PlaybackCompletionDateComparator());
return items;
}
@ -774,4 +783,48 @@ public final class DBReader {
return media;
}
/**
* Returns the flattr queue as a List of FlattrThings. The list consists of Feeds and FeedItems.
*
* @param context A context that is used for opening a database connection.
* @return The flattr queue as a List.
*/
public static List<FlattrThing> getFlattrQueue(Context context) {
PodDBAdapter adapter = new PodDBAdapter(context);
adapter.open();
List<FlattrThing> result = new ArrayList<FlattrThing>();
// load feeds
Cursor feedCursor = adapter.getFeedsInFlattrQueueCursor();
if (feedCursor.moveToFirst()) {
do {
result.add(extractFeedFromCursorRow(adapter, feedCursor));
} while (feedCursor.moveToNext());
}
feedCursor.close();
//load feed items
Cursor feedItemCursor = adapter.getFeedItemsInFlattrQueueCursor();
result.addAll(extractItemlistFromCursor(adapter, feedItemCursor));
feedItemCursor.close();
adapter.close();
Log.d(TAG, "Returning flattrQueueIterator for queue with " + result.size() + " items.");
return result;
}
/**
* Returns true if the flattr queue is empty.
*
* @param context A context that is used for opening a database connection.
*/
public static boolean getFlattrQueueEmpty(Context context) {
PodDBAdapter adapter = new PodDBAdapter(context);
adapter.open();
boolean empty = adapter.getFlattrQueueSize() == 0;
adapter.close();
return empty;
}
}

View File

@ -4,17 +4,20 @@ import android.content.Context;
import android.content.Intent;
import android.database.Cursor;
import android.util.Log;
import de.danoeh.antennapod.asynctask.FlattrClickWorker;
import de.danoeh.antennapod.asynctask.FlattrStatusFetcher;
import de.danoeh.antennapod.AppConfig;
import de.danoeh.antennapod.feed.*;
import de.danoeh.antennapod.preferences.UserPreferences;
import de.danoeh.antennapod.service.GpodnetSyncService;
import de.danoeh.antennapod.service.PlaybackService;
import de.danoeh.antennapod.service.playback.PlaybackService;
import de.danoeh.antennapod.service.download.DownloadStatus;
import de.danoeh.antennapod.util.DownloadError;
import de.danoeh.antennapod.util.NetworkUtils;
import de.danoeh.antennapod.util.QueueAccess;
import de.danoeh.antennapod.util.comparator.FeedItemPubdateComparator;
import de.danoeh.antennapod.util.exception.MediaFileNotFoundException;
import de.danoeh.antennapod.util.flattr.FlattrUtils;
import java.util.*;
import java.util.concurrent.*;
@ -151,6 +154,12 @@ public final class DBTasks {
}
isRefreshing.set(false);
if (AppConfig.DEBUG) Log.d(TAG, "Flattring all pending things.");
new FlattrClickWorker(context, FlattrClickWorker.FLATTR_NOTIFICATION).executeSync(); // flattr pending things
if (AppConfig.DEBUG) Log.d(TAG, "Fetching flattr status.");
new FlattrStatusFetcher(context).start();
GpodnetSyncService.sendSyncIntent(context);
autodownloadUndownloadedItems(context);
}
@ -797,4 +806,34 @@ public final class DBTasks {
}
}
/**
* Adds the given FeedItem to the flattr queue if the user is logged in. Otherwise, a dialog
* will be opened that lets the user go either to the login screen or the website of the flattr thing.
* @param context
* @param item
*/
public static void flattrItemIfLoggedIn(Context context, FeedItem item) {
if (FlattrUtils.hasToken()) {
item.getFlattrStatus().setFlattrQueue();
DBWriter.setFlattredStatus(context, item, true);
} else {
FlattrUtils.showNoTokenDialog(context, item.getPaymentLink());
}
}
/**
* Adds the given Feed to the flattr queue if the user is logged in. Otherwise, a dialog
* will be opened that lets the user go either to the login screen or the website of the flattr thing.
* @param context
* @param feed
*/
public static void flattrFeedIfLoggedIn(Context context, Feed feed) {
if (FlattrUtils.hasToken()) {
feed.getFlattrStatus().setFlattrQueue();
DBWriter.setFlattredStatus(context, feed, true);
} else {
FlattrUtils.showNoTokenDialog(context, feed.getPaymentLink());
}
}
}

View File

@ -1,15 +1,5 @@
package de.danoeh.antennapod.storage;
import java.io.File;
import java.util.Date;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
import java.util.concurrent.ThreadFactory;
import android.content.Context;
import android.content.Intent;
import android.content.SharedPreferences;
@ -17,13 +7,29 @@ import android.database.Cursor;
import android.preference.PreferenceManager;
import android.util.Log;
import de.danoeh.antennapod.AppConfig;
import de.danoeh.antennapod.asynctask.FlattrClickWorker;
import de.danoeh.antennapod.feed.*;
import de.danoeh.antennapod.preferences.GpodnetPreferences;
import de.danoeh.antennapod.preferences.PlaybackPreferences;
import de.danoeh.antennapod.service.GpodnetSyncService;
import de.danoeh.antennapod.service.PlaybackService;
import de.danoeh.antennapod.service.download.DownloadStatus;
import de.danoeh.antennapod.service.playback.PlaybackService;
import de.danoeh.antennapod.util.QueueAccess;
import de.danoeh.antennapod.util.flattr.FlattrStatus;
import de.danoeh.antennapod.util.flattr.FlattrThing;
import de.danoeh.antennapod.util.flattr.SimpleFlattrThing;
import org.shredzone.flattr4j.model.Flattr;
import java.io.File;
import java.io.UnsupportedEncodingException;
import java.net.URLEncoder;
import java.util.Date;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
import java.util.concurrent.ThreadFactory;
/**
* Provides methods for writing data to AntennaPod's database.
@ -222,6 +228,9 @@ public class DBWriter {
if (AppConfig.DEBUG)
Log.d(TAG, "Adding new item to playback history");
media.setPlaybackCompletionDate(new Date());
// reset played_duration to 0 so that it behaves correctly when the episode is played again
media.setPlayedDuration(0);
PodDBAdapter adapter = new PodDBAdapter(context);
adapter.open();
adapter.setFeedMediaPlaybackCompletionDate(media);
@ -446,7 +455,7 @@ public class DBWriter {
});
}
/**
* Moves the specified item to the top of the queue.
*
@ -472,7 +481,7 @@ public class DBWriter {
}
});
}
/**
* Moves the specified item to the bottom of the queue.
*
@ -491,7 +500,7 @@ public class DBWriter {
for (long id : queueIdList) {
if (id == itemId) {
moveQueueItemHelper(context, currentLocation, queueIdList.size() - 1,
broadcastUpdate);
broadcastUpdate);
return;
}
currentLocation++;
@ -500,7 +509,7 @@ public class DBWriter {
}
});
}
/**
* Changes the position of a FeedItem in the queue.
*
@ -524,7 +533,7 @@ public class DBWriter {
/**
* Changes the position of a FeedItem in the queue.
*
* <p/>
* This function must be run using the ExecutorService (dbExec).
*
* @param context A context that is used for opening a database connection.
@ -535,7 +544,7 @@ public class DBWriter {
* @throws IndexOutOfBoundsException if (to < 0 || to >= queue.size()) || (from < 0 || from >= queue.size())
*/
private static void moveQueueItemHelper(final Context context, final int from,
final int to, final boolean broadcastUpdate) {
final int to, final boolean broadcastUpdate) {
final PodDBAdapter adapter = new PodDBAdapter(context);
adapter.open();
final List<FeedItem> queue = DBReader
@ -823,4 +832,125 @@ public class DBWriter {
}
return false;
}
/**
* Saves the FlattrStatus of a FeedItem object in the database.
*
* @param startFlattrClickWorker true if FlattrClickWorker should be started after the FlattrStatus has been saved
*/
public static Future<?> setFeedItemFlattrStatus(final Context context,
final FeedItem item,
final boolean startFlattrClickWorker) {
return dbExec.submit(new Runnable() {
@Override
public void run() {
PodDBAdapter adapter = new PodDBAdapter(context);
adapter.open();
adapter.setFeedItemFlattrStatus(item);
adapter.close();
if (startFlattrClickWorker) {
new FlattrClickWorker(context, FlattrClickWorker.FLATTR_TOAST).executeAsync();
}
}
});
}
/**
* Saves the FlattrStatus of a Feed object in the database.
*
* @param startFlattrClickWorker true if FlattrClickWorker should be started after the FlattrStatus has been saved
*/
private static Future<?> setFeedFlattrStatus(final Context context,
final Feed feed,
final boolean startFlattrClickWorker) {
return dbExec.submit(new Runnable() {
@Override
public void run() {
PodDBAdapter adapter = new PodDBAdapter(context);
adapter.open();
adapter.setFeedFlattrStatus(feed);
adapter.close();
if (startFlattrClickWorker) {
new FlattrClickWorker(context, FlattrClickWorker.FLATTR_TOAST).executeAsync();
}
}
});
}
/**
* format an url for querying the database
* (postfix a / and apply percent-encoding)
*/
private static String formatURIForQuery(String uri) {
try {
return URLEncoder.encode(uri.endsWith("/") ? uri.substring(0, uri.length() - 1) : uri, "UTF-8");
} catch (UnsupportedEncodingException e) {
Log.e(TAG, e.getMessage());
return "";
}
}
/**
* Set flattr status of the passed thing (either a FeedItem or a Feed)
*
* @param context
* @param thing
* @param startFlattrClickWorker true if FlattrClickWorker should be started after the FlattrStatus has been saved
* @return
*/
public static Future<?> setFlattredStatus(Context context, FlattrThing thing, boolean startFlattrClickWorker) {
// must propagate this to back db
if (thing instanceof FeedItem)
return setFeedItemFlattrStatus(context, (FeedItem) thing, startFlattrClickWorker);
else if (thing instanceof Feed)
return setFeedFlattrStatus(context, (Feed) thing, startFlattrClickWorker);
else if (thing instanceof SimpleFlattrThing) {
} // SimpleFlattrThings are generated on the fly and do not have DB backing
else
Log.e(TAG, "flattrQueue processing - thing is neither FeedItem nor Feed nor SimpleFlattrThing");
return null;
}
/**
* Reset flattr status to unflattrd for all items
*/
public static Future<?> clearAllFlattrStatus(final Context context) {
Log.d(TAG, "clearAllFlattrStatus()");
return dbExec.submit(new Runnable() {
@Override
public void run() {
PodDBAdapter adapter = new PodDBAdapter(context);
adapter.open();
adapter.clearAllFlattrStatus();
adapter.close();
}
});
}
/**
* Set flattr status of the feeds/feeditems in flattrList to flattred at the given timestamp,
* where the information has been retrieved from the flattr API
*/
public static Future<?> setFlattredStatus(final Context context, final List<Flattr> flattrList) {
Log.d(TAG, "setFlattredStatus to status retrieved from flattr api running with " + flattrList.size() + " items");
// clear flattr status in db
clearAllFlattrStatus(context);
// submit list with flattred things having normalized URLs to db
return dbExec.submit(new Runnable() {
@Override
public void run() {
PodDBAdapter adapter = new PodDBAdapter(context);
adapter.open();
for (Flattr flattr : flattrList) {
adapter.setItemFlattrStatus(formatURIForQuery(flattr.getThing().getUrl()), new FlattrStatus(flattr.getCreated().getTime()));
}
adapter.close();
}
});
}
}

View File

@ -1,8 +1,5 @@
package de.danoeh.antennapod.storage;
import java.util.Arrays;
import java.util.List;
import android.content.ContentValues;
import android.content.Context;
import android.database.Cursor;
@ -16,6 +13,10 @@ import android.util.Log;
import de.danoeh.antennapod.AppConfig;
import de.danoeh.antennapod.feed.*;
import de.danoeh.antennapod.service.download.DownloadStatus;
import de.danoeh.antennapod.util.flattr.FlattrStatus;
import java.util.Arrays;
import java.util.List;
// TODO Remove media column from feeditem table
@ -24,7 +25,7 @@ import de.danoeh.antennapod.service.download.DownloadStatus;
*/
public class PodDBAdapter {
private static final String TAG = "PodDBAdapter";
private static final int DATABASE_VERSION = 10;
private static final int DATABASE_VERSION = 11;
public static final String DATABASE_NAME = "Antennapod.db";
/**
@ -54,6 +55,7 @@ public class PodDBAdapter {
public static final int KEY_IMAGE_INDEX = 11;
public static final int KEY_TYPE_INDEX = 12;
public static final int KEY_FEED_IDENTIFIER_INDEX = 13;
public static final int KEY_FEED_FLATTR_STATUS_INDEX = 14;
// ----------- FeedItem indices
public static final int KEY_CONTENT_ENCODED_INDEX = 2;
public static final int KEY_PUBDATE_INDEX = 3;
@ -62,6 +64,7 @@ public class PodDBAdapter {
public static final int KEY_FEED_INDEX = 9;
public static final int KEY_HAS_SIMPLECHAPTERS_INDEX = 10;
public static final int KEY_ITEM_IDENTIFIER_INDEX = 11;
public static final int KEY_ITEM_FLATTR_STATUS_INDEX = 12;
// ---------- FeedMedia indices
public static final int KEY_DURATION_INDEX = 1;
public static final int KEY_POSITION_INDEX = 5;
@ -69,6 +72,7 @@ public class PodDBAdapter {
public static final int KEY_MIME_TYPE_INDEX = 7;
public static final int KEY_PLAYBACK_COMPLETION_DATE_INDEX = 8;
public static final int KEY_MEDIA_FEEDITEM_INDEX = 9;
public static final int KEY_PLAYED_DURATION_INDEX = 10;
// --------- Download log indices
public static final int KEY_FEEDFILE_INDEX = 1;
public static final int KEY_FEEDFILETYPE_INDEX = 2;
@ -119,12 +123,14 @@ public class PodDBAdapter {
public static final String KEY_HAS_CHAPTERS = "has_simple_chapters";
public static final String KEY_TYPE = "type";
public static final String KEY_ITEM_IDENTIFIER = "item_identifier";
public static final String KEY_FLATTR_STATUS = "flattr_status";
public static final String KEY_FEED_IDENTIFIER = "feed_identifier";
public static final String KEY_REASON_DETAILED = "reason_detailed";
public static final String KEY_DOWNLOADSTATUS_TITLE = "title";
public static final String KEY_CHAPTER_TYPE = "type";
public static final String KEY_PLAYBACK_COMPLETION_DATE = "playback_completion_date";
public static final String KEY_AUTO_DOWNLOAD = "auto_download";
public static final String KEY_PLAYED_DURATION = "played_duration";
// Table names
public static final String TABLE_NAME_FEEDS = "Feeds";
@ -146,7 +152,8 @@ public class PodDBAdapter {
+ KEY_DESCRIPTION + " TEXT," + KEY_PAYMENT_LINK + " TEXT,"
+ KEY_LASTUPDATE + " TEXT," + KEY_LANGUAGE + " TEXT," + KEY_AUTHOR
+ " TEXT," + KEY_IMAGE + " INTEGER," + KEY_TYPE + " TEXT,"
+ KEY_FEED_IDENTIFIER + " TEXT," + KEY_AUTO_DOWNLOAD + " INTEGER DEFAULT 1)";
+ KEY_FEED_IDENTIFIER + " TEXT," + KEY_AUTO_DOWNLOAD + " INTEGER DEFAULT 1,"
+ KEY_FLATTR_STATUS + " INTEGER)";
private static final String CREATE_TABLE_FEED_ITEMS = "CREATE TABLE "
+ TABLE_NAME_FEED_ITEMS + " (" + TABLE_PRIMARY_KEY + KEY_TITLE
@ -154,7 +161,8 @@ public class PodDBAdapter {
+ " INTEGER," + KEY_READ + " INTEGER," + KEY_LINK + " TEXT,"
+ KEY_DESCRIPTION + " TEXT," + KEY_PAYMENT_LINK + " TEXT,"
+ KEY_MEDIA + " INTEGER," + KEY_FEED + " INTEGER,"
+ KEY_HAS_CHAPTERS + " INTEGER," + KEY_ITEM_IDENTIFIER + " TEXT)";
+ KEY_HAS_CHAPTERS + " INTEGER," + KEY_ITEM_IDENTIFIER + " TEXT,"
+ KEY_FLATTR_STATUS + " INTEGER)";
private static final String CREATE_TABLE_FEED_IMAGES = "CREATE TABLE "
+ TABLE_NAME_FEED_IMAGES + " (" + TABLE_PRIMARY_KEY + KEY_TITLE
@ -167,7 +175,8 @@ public class PodDBAdapter {
+ " TEXT," + KEY_DOWNLOADED + " INTEGER," + KEY_POSITION
+ " INTEGER," + KEY_SIZE + " INTEGER," + KEY_MIME_TYPE + " TEXT,"
+ KEY_PLAYBACK_COMPLETION_DATE + " INTEGER,"
+ KEY_FEEDITEM + " INTEGER)";
+ KEY_FEEDITEM + " INTEGER,"
+ KEY_PLAYED_DURATION + " INTEGER)";
private static final String CREATE_TABLE_DOWNLOAD_LOG = "CREATE TABLE "
+ TABLE_NAME_DOWNLOAD_LOG + " (" + TABLE_PRIMARY_KEY + KEY_FEEDFILE
@ -208,6 +217,7 @@ public class PodDBAdapter {
TABLE_NAME_FEEDS + "." + KEY_TYPE,
TABLE_NAME_FEEDS + "." + KEY_FEED_IDENTIFIER,
TABLE_NAME_FEEDS + "." + KEY_AUTO_DOWNLOAD,
TABLE_NAME_FEEDS + "." + KEY_FLATTR_STATUS
};
// column indices for FEED_SEL_STD
@ -226,6 +236,7 @@ public class PodDBAdapter {
public static final int IDX_FEED_SEL_STD_TYPE = 12;
public static final int IDX_FEED_SEL_STD_FEED_IDENTIFIER = 13;
public static final int IDX_FEED_SEL_PREFERENCES_AUTO_DOWNLOAD = 14;
public static final int IDX_FEED_SEL_STD_FLATTR_STATUS = 15;
/**
@ -241,7 +252,8 @@ public class PodDBAdapter {
TABLE_NAME_FEED_ITEMS + "." + KEY_PAYMENT_LINK, KEY_MEDIA,
TABLE_NAME_FEED_ITEMS + "." + KEY_FEED,
TABLE_NAME_FEED_ITEMS + "." + KEY_HAS_CHAPTERS,
TABLE_NAME_FEED_ITEMS + "." + KEY_ITEM_IDENTIFIER};
TABLE_NAME_FEED_ITEMS + "." + KEY_ITEM_IDENTIFIER,
TABLE_NAME_FEED_ITEMS + "." + KEY_FLATTR_STATUS};
/**
* Contains FEEDITEM_SEL_FI_SMALL as comma-separated list. Useful for raw queries.
@ -265,6 +277,7 @@ public class PodDBAdapter {
public static final int IDX_FI_SMALL_FEED = 7;
public static final int IDX_FI_SMALL_HAS_CHAPTERS = 8;
public static final int IDX_FI_SMALL_ITEM_IDENTIFIER = 9;
public static final int IDX_FI_SMALL_FLATTR_STATUS = 10;
/**
* Select id, description and content-encoded column from feeditems.
@ -346,6 +359,10 @@ public class PodDBAdapter {
values.put(KEY_LASTUPDATE, feed.getLastUpdate().getTime());
values.put(KEY_TYPE, feed.getType());
values.put(KEY_FEED_IDENTIFIER, feed.getFeedIdentifier());
Log.d(TAG, "Setting feed with flattr status " + feed.getTitle() + ": " + feed.getFlattrStatus().toLong());
values.put(KEY_FLATTR_STATUS, feed.getFlattrStatus().toLong());
if (feed.getId() == 0) {
// Create new entry
if (AppConfig.DEBUG)
@ -435,6 +452,7 @@ public class PodDBAdapter {
ContentValues values = new ContentValues();
values.put(KEY_POSITION, media.getPosition());
values.put(KEY_DURATION, media.getDuration());
values.put(KEY_PLAYED_DURATION, media.getPlayedDuration());
db.update(TABLE_NAME_FEED_MEDIA, values, KEY_ID + "=?",
new String[]{String.valueOf(media.getId())});
} else {
@ -446,6 +464,7 @@ public class PodDBAdapter {
if (media.getId() != 0) {
ContentValues values = new ContentValues();
values.put(KEY_PLAYBACK_COMPLETION_DATE, media.getPlaybackCompletionDate().getTime());
values.put(KEY_PLAYED_DURATION, media.getPlayedDuration());
db.update(TABLE_NAME_FEED_MEDIA, values, KEY_ID + "=?",
new String[]{String.valueOf(media.getId())});
} else {
@ -469,6 +488,56 @@ public class PodDBAdapter {
db.endTransaction();
}
/**
* Update the flattr status of a feed
*/
public void setFeedFlattrStatus(Feed feed) {
ContentValues values = new ContentValues();
values.put(KEY_FLATTR_STATUS, feed.getFlattrStatus().toLong());
db.update(TABLE_NAME_FEEDS, values, KEY_ID + "=?", new String[]{String.valueOf(feed.getId())});
}
/**
* Get all feeds in the flattr queue.
*/
public Cursor getFeedsInFlattrQueueCursor() {
return db.query(TABLE_NAME_FEEDS, FEED_SEL_STD, KEY_FLATTR_STATUS + "=?",
new String[]{String.valueOf(FlattrStatus.STATUS_QUEUE)},null, null, null);
}
/**
* Get all feed items in the flattr queue.
*/
public Cursor getFeedItemsInFlattrQueueCursor() {
return db.query(TABLE_NAME_FEED_ITEMS, FEEDITEM_SEL_FI_SMALL, KEY_FLATTR_STATUS + "=?",
new String[]{String.valueOf(FlattrStatus.STATUS_QUEUE)},null, null, null);
}
/**
* Counts feeds and feed items in the flattr queue
*/
public int getFlattrQueueSize() {
int res = 0;
Cursor c = db.rawQuery(String.format("SELECT count(*) FROM %s WHERE %s=%s",
TABLE_NAME_FEEDS, KEY_FLATTR_STATUS, String.valueOf(FlattrStatus.STATUS_QUEUE)), null);
if (c.moveToFirst()) {
res = c.getInt(0);
c.close();
} else {
Log.e(TAG, "Unable to determine size of flattr queue: Could not count number of feeds");
}
c = db.rawQuery(String.format("SELECT count(*) FROM %s WHERE %s=%s",
TABLE_NAME_FEED_ITEMS, KEY_FLATTR_STATUS, String.valueOf(FlattrStatus.STATUS_QUEUE)), null);
if (c.moveToFirst()) {
res += c.getInt(0);
c.close();
} else {
Log.e(TAG, "Unable to determine size of flattr queue: Could not count number of feed items");
}
return res;
}
/**
* Updates the download URL of a Feed.
*/
@ -495,6 +564,63 @@ public class PodDBAdapter {
return result;
}
/**
* Update the flattr status of a FeedItem
*/
public void setFeedItemFlattrStatus(FeedItem feedItem) {
ContentValues values = new ContentValues();
values.put(KEY_FLATTR_STATUS, feedItem.getFlattrStatus().toLong());
db.update(TABLE_NAME_FEED_ITEMS, values, KEY_ID + "=?", new String[]{String.valueOf(feedItem.getId())});
}
/**
* Update the flattr status of a feed or feed item specified by its payment link
* and the new flattr status to use
*/
public void setItemFlattrStatus(String url, FlattrStatus status)
{
//Log.d(TAG, "setItemFlattrStatus(" + url + ") = " + status.toString());
ContentValues values = new ContentValues();
values.put(KEY_FLATTR_STATUS, status.toLong());
// regexps in sqlite would be neat!
String[] query_urls = new String[]{
"*" + url + "&*",
"*" + url + "%2F&*",
"*" + url + "",
"*" + url + "%2F"
};
if (db.update(TABLE_NAME_FEEDS, values,
KEY_PAYMENT_LINK + " GLOB ?"
+ " OR " + KEY_PAYMENT_LINK + " GLOB ?"
+ " OR " + KEY_PAYMENT_LINK + " GLOB ?"
+ " OR " + KEY_PAYMENT_LINK + " GLOB ?", query_urls) > 0)
{
Log.i(TAG, "setItemFlattrStatus found match for " + url + " = " + status.toLong() + " in Feeds table");
return;
}
if (db.update(TABLE_NAME_FEED_ITEMS, values,
KEY_PAYMENT_LINK + " GLOB ?"
+ " OR " + KEY_PAYMENT_LINK + " GLOB ?"
+ " OR " + KEY_PAYMENT_LINK + " GLOB ?"
+ " OR " + KEY_PAYMENT_LINK + " GLOB ?", query_urls) > 0)
{
Log.i(TAG, "setItemFlattrStatus found match for " + url + " = " + status.toLong() + " in FeedsItems table");
}
}
/**
* Reset flattr status to unflattrd for all items
*/
public void clearAllFlattrStatus()
{
ContentValues values = new ContentValues();
values.put(KEY_FLATTR_STATUS, 0);
db.update(TABLE_NAME_FEEDS, values, null, null);
db.update(TABLE_NAME_FEED_ITEMS, values, null, null);
}
/**
* Inserts or updates a feeditem entry
*
@ -522,6 +648,7 @@ public class PodDBAdapter {
values.put(KEY_READ, item.isRead());
values.put(KEY_HAS_CHAPTERS, item.getChapters() != null);
values.put(KEY_ITEM_IDENTIFIER, item.getItemIdentifier());
values.put(KEY_FLATTR_STATUS, item.getFlattrStatus().toLong());
if (item.getId() == 0) {
item.setId(db.insert(TABLE_NAME_FEED_ITEMS, null, values));
} else {
@ -711,7 +838,7 @@ public class PodDBAdapter {
*/
public final Cursor getAllFeedsCursor() {
Cursor c = db.query(TABLE_NAME_FEEDS, FEED_SEL_STD, null, null, null, null,
KEY_TITLE + " ASC");
KEY_TITLE + " COLLATE NOCASE ASC");
return c;
}
@ -849,7 +976,7 @@ public class PodDBAdapter {
/**
* Returns a cursor which contains feed media objects with a playback
* completion date in descending order.
* completion date in ascending order.
*
* @param limit The maximum row count of the returned cursor. Must be an
* integer >= 0.
@ -860,8 +987,8 @@ public class PodDBAdapter {
throw new IllegalArgumentException("Limit must be >= 0");
}
Cursor c = db.query(TABLE_NAME_FEED_MEDIA, null,
KEY_PLAYBACK_COMPLETION_DATE + " > 0", null, null,
null, KEY_PLAYBACK_COMPLETION_DATE + " DESC LIMIT " + limit);
KEY_PLAYBACK_COMPLETION_DATE + " > 0 LIMIT " + limit, null, null,
null, null);
return c;
}
@ -1072,7 +1199,7 @@ public class PodDBAdapter {
" COUNT(CASE WHEN position>0 THEN 1 END) AS in_progress," +
" COUNT(CASE WHEN downloaded=1 THEN 1 END) AS episodes_downloaded " +
" FROM FeedItems LEFT JOIN FeedMedia ON FeedItems.id=FeedMedia.feeditem GROUP BY FeedItems.feed)" +
" ON Feeds.id = feed ORDER BY Feeds.title;";
" ON Feeds.id = feed ORDER BY Feeds.title COLLATE NOCASE ASC;";
public Cursor getFeedStatisticsCursor() {
return db.rawQuery(FEED_STATISTICS_QUERY, null);
@ -1170,6 +1297,17 @@ public class PodDBAdapter {
+ " ADD COLUMN " + KEY_AUTO_DOWNLOAD
+ " INTEGER DEFAULT 1");
}
if (oldVersion <= 10) {
db.execSQL("ALTER TABLE " + TABLE_NAME_FEEDS
+ " ADD COLUMN " + KEY_FLATTR_STATUS
+ " INTEGER");
db.execSQL("ALTER TABLE " + TABLE_NAME_FEED_ITEMS
+ " ADD COLUMN " + KEY_FLATTR_STATUS
+ " INTEGER");
db.execSQL("ALTER TABLE " + TABLE_NAME_FEED_MEDIA
+ " ADD COLUMN " + KEY_PLAYED_DURATION
+ " INTEGER");
}
}
}
}

View File

@ -101,7 +101,10 @@ public class NSRSS20 extends Namespace {
}
if (top.equals(GUID) && second.equals(ITEM)) {
state.getCurrentItem().setItemIdentifier(content);
// some feed creators include an empty or non-standard guid-element in their feed, which should be ignored
if (!content.isEmpty()) {
state.getCurrentItem().setItemIdentifier(content);
}
} else if (top.equals(TITLE)) {
if (second.equals(ITEM)) {
state.getCurrentItem().setTitle(content);

View File

@ -0,0 +1,68 @@
package de.danoeh.antennapod.util.flattr;
import java.util.Calendar;
public class FlattrStatus {
public static final int STATUS_UNFLATTERED = 0;
public static final int STATUS_QUEUE = 1;
public static final int STATUS_FLATTRED = 2;
private int status = STATUS_UNFLATTERED;
private Calendar lastFlattred;
public FlattrStatus() {
status = STATUS_UNFLATTERED;
lastFlattred = Calendar.getInstance();
}
public FlattrStatus(long status) {
lastFlattred = Calendar.getInstance();
fromLong(status);
}
public void setFlattred() {
status = STATUS_FLATTRED;
lastFlattred = Calendar.getInstance();
}
public void setUnflattred() {
status = STATUS_UNFLATTERED;
}
public boolean getUnflattred() {
return status == STATUS_UNFLATTERED;
}
public void setFlattrQueue() {
if (flattrable())
status = STATUS_QUEUE;
}
public void fromLong(long status) {
if (status == STATUS_UNFLATTERED || status == STATUS_QUEUE)
this.status = (int) status;
else {
this.status = STATUS_FLATTRED;
lastFlattred.setTimeInMillis(status);
}
}
public long toLong() {
if (status == STATUS_UNFLATTERED || status == STATUS_QUEUE)
return status;
else {
return lastFlattred.getTimeInMillis();
}
}
public boolean flattrable() {
Calendar firstOfMonth = Calendar.getInstance();
firstOfMonth.set(Calendar.DAY_OF_MONTH, Calendar.getInstance().getActualMinimum(Calendar.DAY_OF_MONTH));
return (status == STATUS_UNFLATTERED) || (status == STATUS_FLATTRED && firstOfMonth.after(lastFlattred) );
}
public boolean getFlattrQueue() {
return status == STATUS_QUEUE;
}
}

View File

@ -0,0 +1,9 @@
package de.danoeh.antennapod.util.flattr;
import de.danoeh.antennapod.util.flattr.FlattrStatus;
public interface FlattrThing {
public String getTitle();
public String getPaymentLink();
public FlattrStatus getFlattrStatus();
}

View File

@ -1,9 +1,16 @@
package de.danoeh.antennapod.util.flattr;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.Date;
import java.util.EnumSet;
import java.util.List;
import java.util.ListIterator;
import java.util.TimeZone;
import org.shredzone.flattr4j.FlattrService;
import org.shredzone.flattr4j.exception.FlattrException;
import org.shredzone.flattr4j.model.Flattr;
import org.shredzone.flattr4j.model.Thing;
import org.shredzone.flattr4j.oauth.AccessToken;
import org.shredzone.flattr4j.oauth.AndroidAuthenticator;
@ -23,6 +30,7 @@ import de.danoeh.antennapod.PodcastApp;
import de.danoeh.antennapod.R;
import de.danoeh.antennapod.activity.FlattrAuthActivity;
import de.danoeh.antennapod.asynctask.FlattrTokenFetcher;
import de.danoeh.antennapod.storage.DBWriter;
/** Utility methods for doing something with flattr. */
@ -119,6 +127,58 @@ public class FlattrUtils {
Log.e(TAG, "clickUrl was called with null access token");
}
}
public static List<Flattr> retrieveFlattredThings()
throws FlattrException {
ArrayList<Flattr> myFlattrs = new ArrayList<Flattr>();
if (hasToken()) {
FlattrService fs = FlattrServiceCreator.getService(retrieveToken());
Calendar firstOfMonth = Calendar.getInstance(TimeZone.getTimeZone("UTC"));
firstOfMonth.set(Calendar.MILLISECOND, 0);
firstOfMonth.set(Calendar.SECOND, 0);
firstOfMonth.set(Calendar.MINUTE, 0);
firstOfMonth.set(Calendar.HOUR_OF_DAY, 0);
firstOfMonth.set(Calendar.DAY_OF_MONTH, Calendar.getInstance().getActualMinimum(Calendar.DAY_OF_MONTH));
Date firstOfMonthDate = firstOfMonth.getTime();
// subscriptions some times get flattrd slightly before midnight - give it an hour leeway
firstOfMonthDate = new Date(firstOfMonthDate.getTime() - 60*60*1000);
final int FLATTR_COUNT = 30;
final int FLATTR_MAXPAGE = 5;
int page = 0;
do {
myFlattrs.ensureCapacity(FLATTR_COUNT*(page+1));
for (Flattr fl: fs.getMyFlattrs(FLATTR_COUNT, page)) {
if (fl.getCreated().after(firstOfMonthDate))
myFlattrs.add(fl);
else
break;
}
page++;
}
while (myFlattrs.get(myFlattrs.size()-1).getCreated().after( firstOfMonthDate ) && page < FLATTR_MAXPAGE);
if (AppConfig.DEBUG) {
Log.d(TAG, "Got my flattrs list of length " + Integer.toString(myFlattrs.size()) + " comparison date" + firstOfMonthDate);
for (Flattr fl: myFlattrs) {
Thing thing = fl.getThing();
Log.d(TAG, "Flattr thing: " + fl.getThingId() + " name: " + thing.getTitle() + " url: " + thing.getUrl() + " on: " + fl.getCreated());
}
}
} else {
Log.e(TAG, "retrieveFlattrdThings was called with null access token");
}
return myFlattrs;
}
public static void handleCallback(Context context, Uri uri) {
AndroidAuthenticator auth = createAuthenticator();
@ -131,7 +191,8 @@ public class FlattrUtils {
deleteToken();
FlattrServiceCreator.deleteFlattrService();
showRevokeDialog(context);
}
DBWriter.clearAllFlattrStatus(context);
}
// ------------------------------------------------ DIALOGS

Some files were not shown because too many files have changed in this diff Show More