Compare commits
6 Commits
Author | SHA1 | Date | |
---|---|---|---|
77e657d37c
|
|||
20407d9cac
|
|||
dbd4b26a65
|
|||
ac5aee20de
|
|||
32844223fc
|
|||
d01e87bf14
|
@ -10,8 +10,8 @@ android {
|
||||
applicationId "org.mosad.teapod"
|
||||
minSdkVersion 23
|
||||
targetSdkVersion 30
|
||||
versionCode 2000 //00.02.000
|
||||
versionName "0.2.0"
|
||||
versionCode 2100 //00.02.100
|
||||
versionName "0.2.1"
|
||||
|
||||
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
|
||||
resValue "string", "build_time", buildTime()
|
||||
@ -51,7 +51,7 @@ dependencies {
|
||||
implementation 'androidx.security:security-crypto:1.1.0-alpha02'
|
||||
implementation 'androidx.legacy:legacy-support-v4:1.0.0'
|
||||
|
||||
implementation 'com.google.android.material:material:1.3.0-alpha03'
|
||||
implementation 'com.google.android.material:material:1.3.0-alpha04'
|
||||
implementation 'com.google.code.gson:gson:2.8.6'
|
||||
implementation 'com.google.android.exoplayer:exoplayer-core:2.12.1'
|
||||
implementation 'com.google.android.exoplayer:exoplayer-hls:2.12.1'
|
||||
|
@ -110,18 +110,18 @@ object AoDParser {
|
||||
* get a media by it's ID (int)
|
||||
* @return Media
|
||||
*/
|
||||
fun getMediaById(mediaId: Int): Media {
|
||||
suspend fun getMediaById(mediaId: Int): Media {
|
||||
val media = mediaList.first { it.id == mediaId }
|
||||
|
||||
if (media.episodes.isEmpty()) {
|
||||
loadStreams(media)
|
||||
loadStreams(media).join()
|
||||
}
|
||||
|
||||
return media
|
||||
}
|
||||
|
||||
// TODO don't use jsoup here
|
||||
fun sendCallback(callbackPath: String) = GlobalScope.launch {
|
||||
fun sendCallback(callbackPath: String) = GlobalScope.launch(Dispatchers.IO) {
|
||||
val headers = mutableMapOf(
|
||||
Pair("Accept", "application/json, text/javascript, */*; q=0.01"),
|
||||
Pair("Accept-Language", "de,en-US;q=0.7,en;q=0.3"),
|
||||
@ -131,13 +131,11 @@ object AoDParser {
|
||||
)
|
||||
|
||||
try {
|
||||
withContext(Dispatchers.IO) {
|
||||
Jsoup.connect(baseUrl + callbackPath)
|
||||
.ignoreContentType(true)
|
||||
.cookies(sessionCookies)
|
||||
.headers(headers)
|
||||
.execute()
|
||||
}
|
||||
} catch (ex: IOException) {
|
||||
Log.e(javaClass.name, "Callback for $callbackPath failed.", ex)
|
||||
}
|
||||
@ -213,16 +211,14 @@ object AoDParser {
|
||||
* load streams for the media path, movies have one episode
|
||||
* @param media is used as call ba reference
|
||||
*/
|
||||
private fun loadStreams(media: Media) = runBlocking {
|
||||
private suspend fun loadStreams(media: Media) = GlobalScope.launch(Dispatchers.IO) {
|
||||
if (sessionCookies.isEmpty()) login()
|
||||
|
||||
if (!loginSuccess) {
|
||||
Log.w(javaClass.name, "Login, was not successful.")
|
||||
return@runBlocking
|
||||
return@launch
|
||||
}
|
||||
|
||||
withContext(Dispatchers.Default) {
|
||||
|
||||
// get the media page
|
||||
val res = Jsoup.connect(baseUrl + media.link)
|
||||
.cookies(sessionCookies)
|
||||
@ -306,7 +302,7 @@ object AoDParser {
|
||||
}
|
||||
}
|
||||
|
||||
// parse additional information for tv shows
|
||||
// parse additional information for tv shows the episode title (description) is loaded from the "api"
|
||||
if (media.type == MediaType.TVSHOW) {
|
||||
res.select("div.three-box-container > div.episodebox").forEach { episodebox ->
|
||||
// make sure the episode has a streaming link
|
||||
@ -324,8 +320,6 @@ object AoDParser {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@ -336,7 +330,7 @@ object AoDParser {
|
||||
return CompletableDeferred(AoDObject(listOf()))
|
||||
}
|
||||
|
||||
return GlobalScope.async {
|
||||
return GlobalScope.async(Dispatchers.IO) {
|
||||
val headers = mutableMapOf(
|
||||
Pair("Accept", "application/json, text/javascript, */*; q=0.01"),
|
||||
Pair("Accept-Language", "de,en-US;q=0.7,en;q=0.3"),
|
||||
|
@ -29,6 +29,7 @@ import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.withContext
|
||||
import org.mosad.teapod.R
|
||||
import org.mosad.teapod.preferences.Preferences
|
||||
import org.mosad.teapod.util.DataTypes
|
||||
import org.mosad.teapod.util.Episode
|
||||
import java.util.*
|
||||
import java.util.concurrent.TimeUnit
|
||||
@ -44,6 +45,7 @@ class PlayerActivity : AppCompatActivity() {
|
||||
private lateinit var gestureDetector: GestureDetectorCompat
|
||||
private lateinit var timerUpdates: TimerTask
|
||||
|
||||
private var nextEpManually = false
|
||||
private var playWhenReady = true
|
||||
private var currentWindow = 0
|
||||
private var playbackPosition: Long = 0
|
||||
@ -129,14 +131,9 @@ class PlayerActivity : AppCompatActivity() {
|
||||
dataSourceFactory = DefaultDataSourceFactory(this, Util.getUserAgent(this, "Teapod"))
|
||||
controller = video_view.findViewById(R.id.exo_controller)
|
||||
|
||||
val mediaSource = HlsMediaSource.Factory(dataSourceFactory)
|
||||
.createMediaSource(MediaItem.fromUri(Uri.parse(autoSelectStream(model.currentEpisode))))
|
||||
controller.isAnimationEnabled = false // disable controls (time-bar) animation
|
||||
|
||||
player.playWhenReady = playWhenReady
|
||||
player.setMediaSource(mediaSource)
|
||||
player.seekTo(playbackPosition)
|
||||
player.prepare()
|
||||
|
||||
player.addListener(object : Player.EventListener {
|
||||
override fun onPlaybackStateChanged(state: Int) {
|
||||
super.onPlaybackStateChanged(state)
|
||||
@ -154,14 +151,16 @@ class PlayerActivity : AppCompatActivity() {
|
||||
}
|
||||
|
||||
if (state == ExoPlayer.STATE_ENDED && model.nextEpisode != null && Preferences.autoplay) {
|
||||
if (nextEpManually) {
|
||||
nextEpManually = false
|
||||
} else {
|
||||
playNextEpisode()
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
controller.isAnimationEnabled = false // disable controls (time-bar) animation
|
||||
exo_text_title.text = model.currentEpisode.title // set media title
|
||||
playCurrentMedia(true)
|
||||
}
|
||||
|
||||
@SuppressLint("ClickableViewAccessibility")
|
||||
@ -187,6 +186,7 @@ class PlayerActivity : AppCompatActivity() {
|
||||
rwd_10.setOnButtonClickListener { rewind() }
|
||||
ffwd_10.setOnButtonClickListener { fastForward() }
|
||||
button_next_ep.setOnClickListener { playNextEpisode() }
|
||||
button_next_ep_c.setOnClickListener { playNextEpisode() }
|
||||
}
|
||||
|
||||
private fun initTimeUpdates() {
|
||||
@ -299,18 +299,37 @@ class PlayerActivity : AppCompatActivity() {
|
||||
}
|
||||
}
|
||||
|
||||
private fun playNextEpisode() = model.nextEpisode?.let { nextEp ->
|
||||
// update the gui
|
||||
exo_text_title.text = nextEp.title
|
||||
private fun playNextEpisode() = model.nextEpisode?.let {
|
||||
model.nextEpisode() // current = next, next = new or null
|
||||
hideButtonNextEp()
|
||||
|
||||
nextEpManually = true
|
||||
playCurrentMedia(false)
|
||||
}
|
||||
|
||||
/**
|
||||
* start playing a episode
|
||||
* Note: movies are episodes too!
|
||||
*/
|
||||
private fun playCurrentMedia(seekToPosition: Boolean) {
|
||||
// update the gui
|
||||
exo_text_title.text = if (model.media.type == DataTypes.MediaType.TVSHOW) {
|
||||
getString(R.string.component_episode_title, model.currentEpisode.number, model.currentEpisode.description)
|
||||
} else {
|
||||
model.currentEpisode.title
|
||||
}
|
||||
|
||||
if (model.nextEpisode == null) {
|
||||
button_next_ep_c.visibility = View.GONE
|
||||
}
|
||||
|
||||
player.clearMediaItems() //remove previous item
|
||||
val mediaSource = HlsMediaSource.Factory(dataSourceFactory)
|
||||
.createMediaSource(MediaItem.fromUri(Uri.parse(autoSelectStream(nextEp))))
|
||||
val mediaSource = HlsMediaSource.Factory(dataSourceFactory).createMediaSource(
|
||||
MediaItem.fromUri(Uri.parse(autoSelectStream(model.currentEpisode)))
|
||||
)
|
||||
if (seekToPosition) player.seekTo(playbackPosition)
|
||||
player.setMediaSource(mediaSource)
|
||||
player.prepare()
|
||||
|
||||
model.nextEpisode()
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -1,6 +1,7 @@
|
||||
package org.mosad.teapod.player
|
||||
|
||||
import androidx.lifecycle.ViewModel
|
||||
import kotlinx.coroutines.runBlocking
|
||||
import org.mosad.teapod.parser.AoDParser
|
||||
import org.mosad.teapod.ui.fragments.MediaFragment
|
||||
import org.mosad.teapod.util.DataTypes
|
||||
@ -25,7 +26,10 @@ class PlayerViewModel : ViewModel() {
|
||||
mediaId = iMediaId
|
||||
episodeId = iEpisodeId
|
||||
|
||||
runBlocking {
|
||||
media = AoDParser.getMediaById(mediaId)
|
||||
}
|
||||
|
||||
currentEpisode = media.episodes.first { it.id == episodeId }
|
||||
nextEpisode = selectNextEpisode()
|
||||
}
|
||||
|
@ -49,12 +49,12 @@ class MediaFragment(private val mediaId: Int) : Fragment() {
|
||||
super.onViewCreated(view, savedInstanceState)
|
||||
binding.frameLoading.visibility = View.VISIBLE
|
||||
|
||||
GlobalScope.launch {
|
||||
GlobalScope.launch(Dispatchers.Main) {
|
||||
// load the streams for the selected media
|
||||
media = AoDParser.getMediaById(mediaId)
|
||||
tmdb = TMDBApiController().search(media.info.title, media.type)
|
||||
|
||||
withContext(Dispatchers.Main) {
|
||||
if (this@MediaFragment.isAdded) {
|
||||
updateGUI()
|
||||
initActions()
|
||||
}
|
||||
|
@ -3,9 +3,7 @@ package org.mosad.teapod.util
|
||||
import android.util.Log
|
||||
import com.google.gson.JsonObject
|
||||
import com.google.gson.JsonParser
|
||||
import kotlinx.coroutines.GlobalScope
|
||||
import kotlinx.coroutines.async
|
||||
import kotlinx.coroutines.runBlocking
|
||||
import kotlinx.coroutines.*
|
||||
import java.net.URL
|
||||
import java.net.URLEncoder
|
||||
import org.mosad.teapod.util.DataTypes.MediaType
|
||||
@ -22,12 +20,12 @@ class TMDBApiController {
|
||||
|
||||
private val imageUrl = "https://image.tmdb.org/t/p/w500"
|
||||
|
||||
fun search(title: String, type: MediaType): TMDBResponse {
|
||||
suspend fun search(title: String, type: MediaType): TMDBResponse {
|
||||
val searchTerm = title.replace("(Sub)", "").trim()
|
||||
|
||||
return when (type) {
|
||||
MediaType.MOVIE -> searchMovie(searchTerm)
|
||||
MediaType.TVSHOW -> searchTVShow(searchTerm)
|
||||
MediaType.MOVIE -> searchMovie(searchTerm).await()
|
||||
MediaType.TVSHOW -> searchTVShow(searchTerm).await()
|
||||
else -> {
|
||||
Log.e(javaClass.name, "Wrong Type: $type")
|
||||
TMDBResponse()
|
||||
@ -36,14 +34,14 @@ class TMDBApiController {
|
||||
|
||||
}
|
||||
|
||||
fun searchTVShow(title: String) = runBlocking {
|
||||
fun searchTVShow(title: String): Deferred<TMDBResponse> {
|
||||
val url = URL("$searchTVUrl$preparedParameters&query=${URLEncoder.encode(title, "UTF-8")}")
|
||||
|
||||
GlobalScope.async {
|
||||
return GlobalScope.async {
|
||||
val response = JsonParser.parseString(url.readText()).asJsonObject
|
||||
//println(response)
|
||||
|
||||
return@async if (response.get("total_results").asInt > 0) {
|
||||
if (response.get("total_results").asInt > 0) {
|
||||
response.get("results").asJsonArray.first().asJsonObject.let {
|
||||
val id = getStringNotNull(it, "id").toInt()
|
||||
val overview = getStringNotNull(it, "overview")
|
||||
@ -55,18 +53,17 @@ class TMDBApiController {
|
||||
} else {
|
||||
TMDBResponse()
|
||||
}
|
||||
}.await()
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
fun searchMovie(title: String) = runBlocking {
|
||||
fun searchMovie(title: String): Deferred<TMDBResponse> {
|
||||
val url = URL("$searchMovieUrl$preparedParameters&query=${URLEncoder.encode(title, "UTF-8")}")
|
||||
|
||||
GlobalScope.async {
|
||||
return GlobalScope.async {
|
||||
val response = JsonParser.parseString(url.readText()).asJsonObject
|
||||
//println(response)
|
||||
|
||||
return@async if (response.get("total_results").asInt > 0) {
|
||||
if (response.get("total_results").asInt > 0) {
|
||||
response.get("results").asJsonArray.first().asJsonObject.let {
|
||||
val id = getStringNotNull(it,"id").toInt()
|
||||
val overview = getStringNotNull(it,"overview")
|
||||
@ -79,9 +76,7 @@ class TMDBApiController {
|
||||
} else {
|
||||
TMDBResponse()
|
||||
}
|
||||
|
||||
|
||||
}.await()
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
5
app/src/main/res/drawable/ic_baseline_skip_next_24.xml
Normal file
5
app/src/main/res/drawable/ic_baseline_skip_next_24.xml
Normal file
@ -0,0 +1,5 @@
|
||||
<vector android:height="24dp" android:tint="#FFFFFF"
|
||||
android:viewportHeight="24" android:viewportWidth="24"
|
||||
android:width="24dp" xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<path android:fillColor="@android:color/white" android:pathData="M6,18l8.5,-6L6,6v12zM16,6v12h2V6h-2z"/>
|
||||
</vector>
|
@ -1,10 +1,5 @@
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="24dp"
|
||||
android:height="24dp"
|
||||
android:viewportWidth="24"
|
||||
android:viewportHeight="24"
|
||||
android:tint="?attr/colorControlNormal">
|
||||
<path
|
||||
android:fillColor="@android:color/white"
|
||||
android:pathData="M4,6L2,6v14c0,1.1 0.9,2 2,2h14v-2L4,20L4,6zM20,2L8,2c-1.1,0 -2,0.9 -2,2v12c0,1.1 0.9,2 2,2h12c1.1,0 2,-0.9 2,-2L22,4c0,-1.1 -0.9,-2 -2,-2zM12,14.5v-9l6,4.5 -6,4.5z"/>
|
||||
<vector android:height="24dp" android:tint="#FFFFFF"
|
||||
android:viewportHeight="24" android:viewportWidth="24"
|
||||
android:width="24dp" xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<path android:fillColor="@android:color/white" android:pathData="M4,6L2,6v14c0,1.1 0.9,2 2,2h14v-2L4,20L4,6zM20,2L8,2c-1.1,0 -2,0.9 -2,2v12c0,1.1 0.9,2 2,2h12c1.1,0 2,-0.9 2,-2L22,4c0,-1.1 -0.9,-2 -2,-2zM12,14.5v-9l6,4.5 -6,4.5z"/>
|
||||
</vector>
|
||||
|
@ -20,12 +20,12 @@
|
||||
app:fastforward_increment="10000"
|
||||
app:rewind_increment="10000" />
|
||||
|
||||
<com.google.android.material.progressindicator.ProgressIndicator
|
||||
<com.google.android.material.progressindicator.CircularProgressIndicator
|
||||
android:id="@+id/loading"
|
||||
style="@style/Widget.MaterialComponents.ProgressIndicator.Circular.Indeterminate"
|
||||
android:layout_width="70dp"
|
||||
android:layout_height="70dp"
|
||||
android:layout_gravity="center"
|
||||
android:indeterminate="true"
|
||||
app:indicatorColor="@color/exo_white"
|
||||
tools:visibility="visible" />
|
||||
|
||||
|
@ -167,12 +167,13 @@
|
||||
android:background="?themePrimary"
|
||||
android:visibility="gone">
|
||||
|
||||
<com.google.android.material.progressindicator.ProgressIndicator
|
||||
<com.google.android.material.progressindicator.CircularProgressIndicator
|
||||
android:id="@+id/loadingIndicator"
|
||||
style="@style/Widget.MaterialComponents.ProgressIndicator.Circular.Indeterminate"
|
||||
android:layout_width="70dp"
|
||||
android:layout_height="70dp"
|
||||
android:layout_gravity="center"
|
||||
android:indeterminate="true"
|
||||
app:indicatorColor="?colorPrimary"
|
||||
tools:visibility="visible" />
|
||||
</FrameLayout>
|
||||
|
||||
|
@ -62,6 +62,6 @@
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:ellipsize="end"
|
||||
android:maxLines="2"
|
||||
android:maxLines="3"
|
||||
android:textColor="?textSecondary" />
|
||||
</LinearLayout>
|
@ -1,6 +1,7 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<com.google.android.material.card.MaterialCardView xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:layout_width="195dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:backgroundTint="?themeSecondary"
|
||||
@ -23,7 +24,7 @@
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
app:srcCompat="@color/md_disabled_text_dark_theme" />
|
||||
tools:srcCompat="@color/md_disabled_text_dark_theme" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/text_title"
|
||||
|
@ -108,4 +108,40 @@
|
||||
app:layout_constraintTop_toTopOf="parent" />
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||
|
||||
<androidx.constraintlayout.widget.ConstraintLayout
|
||||
android:id="@+id/exo_bottom_controls"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="42dp"
|
||||
android:layout_gravity="bottom"
|
||||
android:layout_marginStart="12dp"
|
||||
android:layout_marginEnd="12dp"
|
||||
android:layout_marginBottom="7dp">
|
||||
|
||||
<com.google.android.material.button.MaterialButton
|
||||
android:id="@+id/button_next_ep_c"
|
||||
style="@style/Widget.AppCompat.Button.Borderless"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="@string/episode"
|
||||
android:textAllCaps="false"
|
||||
app:icon="@drawable/ic_baseline_skip_next_24"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent" />
|
||||
|
||||
<com.google.android.material.button.MaterialButton
|
||||
android:id="@+id/button_episodes"
|
||||
style="@style/Widget.AppCompat.Button.Borderless"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginEnd="7dp"
|
||||
android:text="@string/episodes"
|
||||
android:textAllCaps="false"
|
||||
android:visibility="gone"
|
||||
app:icon="@drawable/ic_baseline_video_library_24"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintEnd_toStartOf="@+id/button_next_ep_c"
|
||||
app:layout_constraintTop_toTopOf="parent" />
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||
|
||||
</FrameLayout>
|
@ -16,15 +16,15 @@
|
||||
<string name="button_play">Abspielen</string>
|
||||
<string name="text_episodes_count">%1$d Episoden</string>
|
||||
<string name="text_runtime">%1$d Minuten</string>
|
||||
<string name="component_episode_title">Episode %1$d %2$s</string>
|
||||
<string name="component_episode_title_sub">Episode %1$d %2$s (OmU)</string>
|
||||
<string name="component_episode_title">Flg. %1$d %2$s</string>
|
||||
<string name="component_episode_title_sub">Flg. %1$d %2$s (OmU)</string>
|
||||
|
||||
<!-- settings fragment -->
|
||||
<string name="account">Account</string>
|
||||
<string name="account_login_desc">Zum bearbeiten tippen</string>
|
||||
<string name="info">Info</string>
|
||||
<string name="info_about_desc">Version %1$s (%2$s)</string>
|
||||
<string name="info_about_dialog">Diese App wird unter den Bedingungen der GNU GPL 3 oder höher zur Verfügung gestellt. Weiter Informationen findest du unter: \ngit.mosad.xyz/Seil0/teapod \n\n© 2020 seil0@mosad.xyz</string>
|
||||
<string name="info_about_dialog">Diese App wird unter den Bedingungen der GNU GPL 3 oder höher zur Verfügung gestellt. Weiter Informationen findest du unter: git.mosad.xyz/Seil0/teapod \n\n© 2020 seil0@mosad.xyz</string>
|
||||
<string name="licenses">Lizenzen</string>
|
||||
<string name="settings">Einstellungen</string>
|
||||
<string name="settings_secondary">Bevorzuge alternativen Stream</string>
|
||||
@ -40,7 +40,9 @@
|
||||
<string name="rewind_10">10 Sekunden zurück</string>
|
||||
<string name="play_pause">Abspielen/Pause</string>
|
||||
<string name="forward_10">10 Sekunden vorwärts</string>
|
||||
<string name="next_episode">Nächste Episode</string>
|
||||
<string name="next_episode">Nächste Folge</string>
|
||||
<string name="episode">Folge</string>
|
||||
<string name="episodes">Folgen</string>
|
||||
|
||||
<!-- dialogs -->
|
||||
<string name="save">speichern</string>
|
||||
|
@ -21,8 +21,8 @@
|
||||
<string name="text_age_ex" translatable="false">6</string>
|
||||
<string name="text_episodes_count">%1$d episodes</string>
|
||||
<string name="text_runtime">%1$d Minutes</string>
|
||||
<string name="component_episode_title">Episode %1$d %2$s</string>
|
||||
<string name="component_episode_title_sub">Episode %1$d %2$s (Sub)</string>
|
||||
<string name="component_episode_title">Ep. %1$d %2$s</string>
|
||||
<string name="component_episode_title_sub">Ep. %1$d %2$s (Sub)</string>
|
||||
<string name="component_poster_desc" translatable="false">episode poster</string>
|
||||
<string name="component_watched_desc" translatable="false">already watched</string>
|
||||
|
||||
@ -33,7 +33,7 @@
|
||||
<string name="info">Info</string>
|
||||
<string name="info_about" translatable="false">Teapod by @Seil0</string>
|
||||
<string name="info_about_desc">Version %1$s (%2$s)</string>
|
||||
<string name="info_about_dialog">This app is published under the terms and conditions of the GNU GPL 3 or later. For further information visit: \ngit.mosad.xyz/Seil0/teapod \n\n© 2020 seil0@mosad.xyz</string>
|
||||
<string name="info_about_dialog">This app is published under the terms and conditions of the GNU GPL 3 or later. For further information visit: git.mosad.xyz/Seil0/teapod \n\n© 2020 seil0@mosad.xyz</string>
|
||||
<string name="licenses">Licenses</string>
|
||||
<string name="settings">Settings</string>
|
||||
<string name="settings_secondary">Prefer secondary (sub) stream</string>
|
||||
@ -52,9 +52,11 @@
|
||||
<string name="forward_10">forward 10 seconds</string>
|
||||
<string name="rwd_10_s" translatable="false">- 10 s</string>
|
||||
<string name="fwd_10_s" translatable="false">+ 10 s</string>
|
||||
<string name="next_episode">Next Episode</string>
|
||||
<string name="time_min_sec" translatable="false">%1$02d:%2$02d</string>
|
||||
<string name="time_hour_min_sec" translatable="false">%1$d:%2$02d:%3$02d</string>
|
||||
<string name="next_episode">Next Episode</string>
|
||||
<string name="episode">Episode</string>
|
||||
<string name="episodes">Episodes</string>
|
||||
|
||||
<!-- dialogs -->
|
||||
<string name="save">save</string>
|
||||
|
3
fastlane/metadata/android/de-DE/changelogs/2100.txt
Normal file
3
fastlane/metadata/android/de-DE/changelogs/2100.txt
Normal file
@ -0,0 +1,3 @@
|
||||
* Ein Fehler wurde behoben, bei dem Autoplay Folgen überspringen konnte
|
||||
* Der Player zeigt nun einen Button an, um zur nächsten Folge zu springen
|
||||
* Die UI wurde an einigen Stellen angepasst
|
Reference in New Issue
Block a user