rework how different streams/languages per episode are handled

* potentially support more than 2 streams
* part of language settings in player
This commit is contained in:
Jannik 2020-12-26 14:39:35 +01:00
parent 8f60a30d61
commit 8a43567737
Signed by: Seil0
GPG Key ID: E8459F3723C52C24
7 changed files with 85 additions and 68 deletions

View File

@ -284,55 +284,45 @@ object AoDParser {
//Log.i(javaClass.name, "New csrf token is $csrfToken") //Log.i(javaClass.name, "New csrf token is $csrfToken")
} }
val pl = res.select("input.streamstarter_html5").first() val besides = res.select("div.besides").first()
val primary = pl.attr("data-playlist") val playlists = besides.select("input.streamstarter_html5").map { streamstarter ->
val secondary = pl.attr("data-otherplaylist") parsePlaylistAsync(
val secondaryIsOmU = secondary.contains("OmU", true) streamstarter.attr("data-playlist"),
streamstarter.attr("data-lang")
)
}.awaitAll()
// load primary and secondary playlist playlists.forEach { aod ->
val primaryPlaylist = parsePlaylistAsync(primary) // TODO improve language handling
val secondaryPlaylist = parsePlaylistAsync(secondary) val locale = when (aod.extLanguage) {
"ger" -> Locale.GERMAN
primaryPlaylist.await().playlist.forEach { ep -> "jap" -> Locale.JAPANESE
try { else -> Locale.ROOT
media.episodes.add(Episode(
id = ep.mediaid,
priStreamUrl = ep.sources.first().file,
posterUrl = ep.image,
title = ep.title,
description = ep.description,
number = getNumberFromTitle(ep.title, media.type)
))
} catch (ex: Exception) {
Log.w(javaClass.name, "Could not parse episode information.", ex)
} }
}
Log.i(javaClass.name, "Loading primary playlist finished")
secondaryPlaylist.await().playlist.forEach { ep -> aod.playlist.forEach { ep ->
try { try {
val episode = media.episodes.firstOrNull { it.id == ep.mediaid } if (media.hasEpisode(ep.mediaid)) {
media.getEpisodeById(ep.mediaid).streams.add(
// if media contains already a episode with this id, add as secondary, else add as primary Stream(ep.sources.first().file, locale)
if (episode != null) { )
episode.secStreamUrl = ep.sources.first().file } else {
episode.secStreamOmU = secondaryIsOmU media.episodes.add(Episode(
} else { id = ep.mediaid,
media.episodes.add(Episode( streams = mutableListOf(Stream(ep.sources.first().file, locale)),
id = ep.mediaid, posterUrl = ep.image,
secStreamUrl = ep.sources.first().file, title = ep.title,
secStreamOmU = secondaryIsOmU, description = ep.description,
posterUrl = ep.image, number = getNumberFromTitle(ep.title, media.type)
title = ep.title, ))
description = ep.description, println(getNumberFromTitle(ep.title, media.type))
number = getNumberFromTitle(ep.title, media.type) }
)) } catch (ex: Exception) {
Log.w(javaClass.name, "Could not parse episode information.", ex)
} }
} catch (ex: Exception) {
Log.w(javaClass.name, "Could not parse episode information.", ex)
} }
} }
Log.i(javaClass.name, "Loading secondary playlist finished") Log.i(javaClass.name, "Loaded playlists successfully")
// parse additional info from the media page // parse additional info from the media page
res.select("table.vertical-table").select("tr").forEach { row -> res.select("table.vertical-table").select("tr").forEach { row ->
@ -371,9 +361,9 @@ object AoDParser {
/** /**
* don't use Gson().fromJson() as we don't have any control over the api and it may change * don't use Gson().fromJson() as we don't have any control over the api and it may change
*/ */
private fun parsePlaylistAsync(playlistPath: String): Deferred<AoDObject> { private fun parsePlaylistAsync(playlistPath: String, language: String): Deferred<AoDObject> {
if (playlistPath == "[]") { if (playlistPath == "[]") {
return CompletableDeferred(AoDObject(listOf())) return CompletableDeferred(AoDObject(listOf(), language))
} }
return GlobalScope.async(Dispatchers.IO) { return GlobalScope.async(Dispatchers.IO) {
@ -406,7 +396,9 @@ object AoDParser {
description = it.asJsonObject.get("description").asString, description = it.asJsonObject.get("description").asString,
mediaid = it.asJsonObject.get("mediaid").asInt mediaid = it.asJsonObject.get("mediaid").asInt
) )
}) },
language
)
} }
} }

View File

@ -9,6 +9,8 @@ import org.mosad.teapod.ui.fragments.MediaFragment
import org.mosad.teapod.util.DataTypes import org.mosad.teapod.util.DataTypes
import org.mosad.teapod.util.Episode import org.mosad.teapod.util.Episode
import org.mosad.teapod.util.Media import org.mosad.teapod.util.Media
import java.util.*
import kotlin.collections.ArrayList
import kotlin.properties.Delegates import kotlin.properties.Delegates
/** /**
@ -55,13 +57,10 @@ class PlayerViewModel : ViewModel() {
* If no stream is present, return empty string. * If no stream is present, return empty string.
*/ */
fun autoSelectStream(episode: Episode): String { fun autoSelectStream(episode: Episode): String {
return if ((Preferences.preferSecondary || episode.priStreamUrl.isEmpty()) && episode.secStreamOmU) { return if (Preferences.preferSecondary) {
episode.secStreamUrl episode.getPreferredStream(Locale.JAPANESE).url
} else if (episode.priStreamUrl.isNotEmpty()) {
episode.priStreamUrl
} else { } else {
Log.e(javaClass.name, "No stream url set. ${episode.id}") episode.getPreferredStream(Locale.GERMAN).url
""
} }
} }

View File

@ -1,5 +1,8 @@
package org.mosad.teapod.util package org.mosad.teapod.util
import java.util.*
import kotlin.collections.ArrayList
class DataTypes { class DataTypes {
enum class MediaType { enum class MediaType {
OTHER, OTHER,
@ -47,7 +50,10 @@ data class Media(
val type: DataTypes.MediaType, val type: DataTypes.MediaType,
val info: Info = Info(), val info: Info = Info(),
var episodes: ArrayList<Episode> = arrayListOf() var episodes: ArrayList<Episode> = arrayListOf()
) ) {
fun hasEpisode(id: Int) = episodes.any { it.id == id }
fun getEpisodeById(id: Int) = episodes.first { it.id == id }
}
data class Info( data class Info(
var title: String = "", var title: String = "",
@ -60,23 +66,37 @@ data class Info(
) )
/** /**
* if secStreamOmU == true, then a secondary stream is present
* number = episode number (0..n) * number = episode number (0..n)
*/ */
data class Episode( data class Episode(
val id: Int = 0, val id: Int = 0,
val streams: MutableList<Stream> = mutableListOf(),
var title: String = "", var title: String = "",
var priStreamUrl: String = "",
var secStreamUrl: String = "",
var secStreamOmU: Boolean = false,
var posterUrl: String = "", var posterUrl: String = "",
var description: String = "", var description: String = "",
var shortDesc: String = "", var shortDesc: String = "",
var number: Int = 0, var number: Int = 0,
var watched: Boolean = false, var watched: Boolean = false,
var watchedCallback: String = "" var watchedCallback: String = ""
) {
/**
* get the preferred stream
* @return the preferred stream, if not present use the first stream
*/
fun getPreferredStream(language: Locale) =
streams.firstOrNull { it.language == language } ?: streams.first()
fun hasDub() = streams.any { it.language == Locale.GERMAN }
}
data class Stream(
val url: String,
val language : Locale
) )
/**
* this class is used for tmdb responses
*/
data class TMDBResponse( data class TMDBResponse(
val id: Int = 0, val id: Int = 0,
val title: String = "", val title: String = "",
@ -86,7 +106,13 @@ data class TMDBResponse(
var runtime: Int = 0 var runtime: Int = 0
) )
data class AoDObject(val playlist: List<Playlist>) /**
* this class is used to represent the aod json API?
*/
data class AoDObject(
val playlist: List<Playlist>,
val extLanguage: String
)
data class Playlist( data class Playlist(
val sources: List<Source>, val sources: List<Source>,

View File

@ -23,10 +23,10 @@ class EpisodeItemAdapter(private val episodes: List<Episode>) : RecyclerView.Ada
val context = holder.binding.root.context val context = holder.binding.root.context
val ep = episodes[position] val ep = episodes[position]
val titleText = if (ep.priStreamUrl.isEmpty() && ep.secStreamOmU) { val titleText = if (ep.hasDub()) {
context.getString(R.string.component_episode_title_sub, ep.number, ep.description)
} else {
context.getString(R.string.component_episode_title, ep.number, ep.description) context.getString(R.string.component_episode_title, ep.number, ep.description)
} else {
context.getString(R.string.component_episode_title_sub, ep.number, ep.description)
} }
holder.binding.textEpisodeTitle.text = titleText holder.binding.textEpisodeTitle.text = titleText

View File

@ -22,10 +22,10 @@ class PlayerEpisodeItemAdapter(private val episodes: List<Episode>) : RecyclerVi
val context = holder.binding.root.context val context = holder.binding.root.context
val ep = episodes[position] val ep = episodes[position]
val titleText = if (ep.priStreamUrl.isEmpty() && ep.secStreamOmU) { val titleText = if (ep.hasDub()) {
context.getString(R.string.component_episode_title_sub, ep.number, ep.description)
} else {
context.getString(R.string.component_episode_title, ep.number, ep.description) context.getString(R.string.component_episode_title, ep.number, ep.description)
} else {
context.getString(R.string.component_episode_title_sub, ep.number, ep.description)
} }
holder.binding.textEpisodeTitle2.text = titleText holder.binding.textEpisodeTitle2.text = titleText

View File

@ -28,8 +28,8 @@
<string name="info">Info</string> <string name="info">Info</string>
<string name="info_about_desc">Version %1$s (%2$s)</string> <string name="info_about_desc">Version %1$s (%2$s)</string>
<string name="settings">Einstellungen</string> <string name="settings">Einstellungen</string>
<string name="settings_secondary">Bevorzuge alternativen Stream</string> <string name="settings_secondary">Bevorzuge Japanisch (OmU)</string>
<string name="settings_secondary_desc">Untertitle-Stream verwenden, sofern vorhanden</string> <string name="settings_secondary_desc">Japanisch verwenden, sofern vorhanden</string>
<string name="settings_autoplay">Autoplay</string> <string name="settings_autoplay">Autoplay</string>
<string name="settings_autoplay_desc">Nächste Episode automatisch abspielen</string> <string name="settings_autoplay_desc">Nächste Episode automatisch abspielen</string>
<string name="theme">Design</string> <string name="theme">Design</string>

View File

@ -37,8 +37,8 @@
<string name="info_about" translatable="false">Teapod by @Seil0</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_desc">Version %1$s (%2$s)</string>
<string name="settings">Settings</string> <string name="settings">Settings</string>
<string name="settings_secondary">Prefer secondary (sub) stream</string> <string name="settings_secondary">Prefer japanese (sub)</string>
<string name="settings_secondary_desc">Use the subtitles stream if present</string> <string name="settings_secondary_desc">Use the japanese, if present</string>
<string name="settings_autoplay">Autoplay</string> <string name="settings_autoplay">Autoplay</string>
<string name="settings_autoplay_desc">Play next episode automatically</string> <string name="settings_autoplay_desc">Play next episode automatically</string>
<string name="theme">Theme</string> <string name="theme">Theme</string>