forked from recloudstream/cloudstream
Reworked the internal subtitle API to fix edge-cases when importing subtitles
This commit is contained in:
parent
6d13cf0b01
commit
4b0b6f6f20
5 changed files with 102 additions and 102 deletions
|
@ -48,7 +48,7 @@ android {
|
||||||
targetSdk = 33
|
targetSdk = 33
|
||||||
|
|
||||||
versionCode = 55
|
versionCode = 55
|
||||||
versionName = "3.2.3"
|
versionName = "3.2.5"
|
||||||
|
|
||||||
resValue("string", "app_version", "${defaultConfig.versionName}${versionNameSuffix ?: ""}")
|
resValue("string", "app_version", "${defaultConfig.versionName}${versionNameSuffix ?: ""}")
|
||||||
|
|
||||||
|
@ -190,7 +190,7 @@ dependencies {
|
||||||
// Networking
|
// Networking
|
||||||
// implementation("com.squareup.okhttp3:okhttp:4.9.2")
|
// implementation("com.squareup.okhttp3:okhttp:4.9.2")
|
||||||
// implementation("com.squareup.okhttp3:okhttp-dnsoverhttps:4.9.1")
|
// implementation("com.squareup.okhttp3:okhttp-dnsoverhttps:4.9.1")
|
||||||
implementation("com.github.Blatzar:NiceHttp:0.4.0")
|
implementation("com.github.Blatzar:NiceHttp:0.4.1")
|
||||||
// To fix SSL fuckery on android 9
|
// To fix SSL fuckery on android 9
|
||||||
implementation("org.conscrypt:conscrypt-android:2.2.1")
|
implementation("org.conscrypt:conscrypt-android:2.2.1")
|
||||||
// Util to skip the URI file fuckery 🙏
|
// Util to skip the URI file fuckery 🙏
|
||||||
|
|
|
@ -8,8 +8,7 @@ import android.util.Log
|
||||||
import android.widget.FrameLayout
|
import android.widget.FrameLayout
|
||||||
import androidx.preference.PreferenceManager
|
import androidx.preference.PreferenceManager
|
||||||
import com.google.android.exoplayer2.*
|
import com.google.android.exoplayer2.*
|
||||||
import com.google.android.exoplayer2.C.TRACK_TYPE_AUDIO
|
import com.google.android.exoplayer2.C.*
|
||||||
import com.google.android.exoplayer2.C.TRACK_TYPE_VIDEO
|
|
||||||
import com.google.android.exoplayer2.database.StandaloneDatabaseProvider
|
import com.google.android.exoplayer2.database.StandaloneDatabaseProvider
|
||||||
import com.google.android.exoplayer2.ext.okhttp.OkHttpDataSource
|
import com.google.android.exoplayer2.ext.okhttp.OkHttpDataSource
|
||||||
import com.google.android.exoplayer2.source.*
|
import com.google.android.exoplayer2.source.*
|
||||||
|
@ -89,10 +88,10 @@ class CS3IPlayer : IPlayer {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Tracks reported to be used by exoplayer, since sometimes it has a mind of it's own when selecting subs.
|
* Tracks reported to be used by exoplayer, since sometimes it has a mind of it's own when selecting subs.
|
||||||
* String = lowercase language as set by .setLanguage("_$langId")
|
* String = id
|
||||||
* Boolean = if it's active
|
* Boolean = if it's active
|
||||||
* */
|
* */
|
||||||
private var exoPlayerSelectedTracks = listOf<Pair<String, Boolean>>()
|
private var playerSelectedSubtitleTracks = listOf<Pair<String, Boolean>>()
|
||||||
|
|
||||||
/** isPlaying */
|
/** isPlaying */
|
||||||
private var updateIsPlaying: ((Pair<CSPlayerLoading, CSPlayerLoading>) -> Unit)? = null
|
private var updateIsPlaying: ((Pair<CSPlayerLoading, CSPlayerLoading>) -> Unit)? = null
|
||||||
|
@ -311,12 +310,16 @@ class CS3IPlayer : IPlayer {
|
||||||
* */
|
* */
|
||||||
private fun List<Tracks.Group>.getFormats(): List<Pair<Format, Int>> {
|
private fun List<Tracks.Group>.getFormats(): List<Pair<Format, Int>> {
|
||||||
return this.map {
|
return this.map {
|
||||||
(0 until it.mediaTrackGroup.length).mapNotNull { i ->
|
it.getFormats()
|
||||||
if (it.isSupported)
|
}.flatten()
|
||||||
it.mediaTrackGroup.getFormat(i) to i
|
}
|
||||||
|
|
||||||
|
private fun Tracks.Group.getFormats(): List<Pair<Format, Int>> {
|
||||||
|
return (0 until this.mediaTrackGroup.length).mapNotNull { i ->
|
||||||
|
if (this.isSupported)
|
||||||
|
this.mediaTrackGroup.getFormat(i) to i
|
||||||
else null
|
else null
|
||||||
}
|
}
|
||||||
}.flatten()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun Format.toAudioTrack(): AudioTrack {
|
private fun Format.toAudioTrack(): AudioTrack {
|
||||||
|
@ -361,12 +364,17 @@ class CS3IPlayer : IPlayer {
|
||||||
override fun setPreferredSubtitles(subtitle: SubtitleData?): Boolean {
|
override fun setPreferredSubtitles(subtitle: SubtitleData?): Boolean {
|
||||||
Log.i(TAG, "setPreferredSubtitles init $subtitle")
|
Log.i(TAG, "setPreferredSubtitles init $subtitle")
|
||||||
currentSubtitles = subtitle
|
currentSubtitles = subtitle
|
||||||
|
|
||||||
|
fun getTextTrack(id: String) =
|
||||||
|
exoPlayer?.currentTracks?.groups?.filter { it.type == TRACK_TYPE_TEXT }
|
||||||
|
?.getTrack(id)
|
||||||
|
|
||||||
return (exoPlayer?.trackSelector as? DefaultTrackSelector?)?.let { trackSelector ->
|
return (exoPlayer?.trackSelector as? DefaultTrackSelector?)?.let { trackSelector ->
|
||||||
val name = subtitle?.name
|
if (subtitle == null) {
|
||||||
if (name.isNullOrBlank()) {
|
|
||||||
trackSelector.setParameters(
|
trackSelector.setParameters(
|
||||||
trackSelector.buildUponParameters()
|
trackSelector.buildUponParameters()
|
||||||
.setPreferredTextLanguage(null)
|
.setPreferredTextLanguage(null)
|
||||||
|
.clearOverridesOfType(TRACK_TYPE_TEXT)
|
||||||
)
|
)
|
||||||
} else {
|
} else {
|
||||||
when (subtitleHelper.subtitleStatus(subtitle)) {
|
when (subtitleHelper.subtitleStatus(subtitle)) {
|
||||||
|
@ -380,12 +388,15 @@ class CS3IPlayer : IPlayer {
|
||||||
trackSelector.setParameters(
|
trackSelector.setParameters(
|
||||||
trackSelector.buildUponParameters()
|
trackSelector.buildUponParameters()
|
||||||
.apply {
|
.apply {
|
||||||
if (subtitle.origin == SubtitleOrigin.EMBEDDED_IN_VIDEO)
|
val track = getTextTrack(subtitle.getId())
|
||||||
// The real Language (two letter) is in the url
|
if (track != null) {
|
||||||
// No underscore as the .url is the actual exoplayer designated language
|
setOverrideForType(
|
||||||
setPreferredTextLanguage(subtitle.url)
|
TrackSelectionOverride(
|
||||||
else
|
track.first,
|
||||||
setPreferredTextLanguage("_$name")
|
track.second
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -419,17 +430,8 @@ class CS3IPlayer : IPlayer {
|
||||||
|
|
||||||
override fun getCurrentPreferredSubtitle(): SubtitleData? {
|
override fun getCurrentPreferredSubtitle(): SubtitleData? {
|
||||||
return subtitleHelper.getAllSubtitles().firstOrNull { sub ->
|
return subtitleHelper.getAllSubtitles().firstOrNull { sub ->
|
||||||
exoPlayerSelectedTracks.any {
|
playerSelectedSubtitleTracks.any { (id, isSelected) ->
|
||||||
// When embedded the real language is in .url as the real name is a two letter code
|
isSelected && sub.getId() == id
|
||||||
val realName =
|
|
||||||
if (sub.origin == SubtitleOrigin.EMBEDDED_IN_VIDEO) sub.url else sub.name
|
|
||||||
|
|
||||||
// The replace is needed as exoplayer translates _ to -
|
|
||||||
// Also we prefix the languages with _
|
|
||||||
it.second && it.first.replace("-", "").equals(
|
|
||||||
realName.replace("-", ""),
|
|
||||||
ignoreCase = true
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -883,40 +885,33 @@ class CS3IPlayer : IPlayer {
|
||||||
}
|
}
|
||||||
exoPlayer?.addListener(object : Player.Listener {
|
exoPlayer?.addListener(object : Player.Listener {
|
||||||
override fun onTracksChanged(tracks: Tracks) {
|
override fun onTracksChanged(tracks: Tracks) {
|
||||||
fun Format.isSubtitle(): Boolean {
|
|
||||||
return this.sampleMimeType?.contains("video/") == false &&
|
|
||||||
this.sampleMimeType?.contains("audio/") == false
|
|
||||||
}
|
|
||||||
|
|
||||||
normalSafeApiCall {
|
normalSafeApiCall {
|
||||||
exoPlayerSelectedTracks =
|
val textTracks = tracks.groups.filter { it.type == TRACK_TYPE_TEXT }
|
||||||
tracks.groups.mapNotNull {
|
|
||||||
val format = it.mediaTrackGroup.getFormat(0)
|
|
||||||
if (format.isSubtitle())
|
|
||||||
format.language?.let { lang -> lang to it.isSelected }
|
|
||||||
else null
|
|
||||||
}
|
|
||||||
|
|
||||||
val exoPlayerReportedTracks = tracks.groups.mapNotNull {
|
playerSelectedSubtitleTracks =
|
||||||
// Filter out unsupported tracks
|
textTracks.map { group ->
|
||||||
if (it.isSupported)
|
group.getFormats().mapNotNull { (format, _) ->
|
||||||
it.mediaTrackGroup.getFormat(0)
|
(format.id ?: return@mapNotNull null) to group.isSelected
|
||||||
else
|
}
|
||||||
null
|
}.flatten()
|
||||||
}.mapNotNull {
|
|
||||||
|
val exoPlayerReportedTracks =
|
||||||
|
tracks.groups.filter { it.type == TRACK_TYPE_TEXT }.getFormats()
|
||||||
|
.mapNotNull { (format, _) ->
|
||||||
// Filter out non subs, already used subs and subs without languages
|
// Filter out non subs, already used subs and subs without languages
|
||||||
if (!it.isSubtitle() ||
|
if (format.id == null ||
|
||||||
// Anything starting with - is not embedded
|
format.language == null ||
|
||||||
it.language?.startsWith("-") == true ||
|
format.language?.startsWith("-") == true
|
||||||
it.language == null
|
|
||||||
) return@mapNotNull null
|
) return@mapNotNull null
|
||||||
|
|
||||||
return@mapNotNull SubtitleData(
|
return@mapNotNull SubtitleData(
|
||||||
// Nicer looking displayed names
|
// Nicer looking displayed names
|
||||||
fromTwoLettersToLanguage(it.language!!) ?: it.language!!,
|
fromTwoLettersToLanguage(format.language!!)
|
||||||
|
?: format.language!!,
|
||||||
// See setPreferredTextLanguage
|
// See setPreferredTextLanguage
|
||||||
it.language!!,
|
format.id!!,
|
||||||
SubtitleOrigin.EMBEDDED_IN_VIDEO,
|
SubtitleOrigin.EMBEDDED_IN_VIDEO,
|
||||||
it.sampleMimeType ?: MimeTypes.APPLICATION_SUBRIP,
|
format.sampleMimeType ?: MimeTypes.APPLICATION_SUBRIP,
|
||||||
emptyMap()
|
emptyMap()
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
@ -978,7 +973,7 @@ class CS3IPlayer : IPlayer {
|
||||||
// This is to switch mirrors automatically if the stream has not been fetched, but
|
// This is to switch mirrors automatically if the stream has not been fetched, but
|
||||||
// allow playing the buffer without internet as then the duration is fetched.
|
// allow playing the buffer without internet as then the duration is fetched.
|
||||||
if (error.errorCode == PlaybackException.ERROR_CODE_IO_NETWORK_CONNECTION_FAILED
|
if (error.errorCode == PlaybackException.ERROR_CODE_IO_NETWORK_CONNECTION_FAILED
|
||||||
&& exoPlayer?.duration != C.TIME_UNSET
|
&& exoPlayer?.duration != TIME_UNSET
|
||||||
) {
|
) {
|
||||||
exoPlayer?.prepare()
|
exoPlayer?.prepare()
|
||||||
} else {
|
} else {
|
||||||
|
@ -1137,14 +1132,15 @@ class CS3IPlayer : IPlayer {
|
||||||
val subConfig = MediaItem.SubtitleConfiguration.Builder(Uri.parse(sub.url))
|
val subConfig = MediaItem.SubtitleConfiguration.Builder(Uri.parse(sub.url))
|
||||||
.setMimeType(sub.mimeType)
|
.setMimeType(sub.mimeType)
|
||||||
.setLanguage("_${sub.name}")
|
.setLanguage("_${sub.name}")
|
||||||
.setSelectionFlags(C.SELECTION_FLAG_DEFAULT)
|
.setId(sub.getId())
|
||||||
|
.setSelectionFlags(SELECTION_FLAG_DEFAULT)
|
||||||
.build()
|
.build()
|
||||||
when (sub.origin) {
|
when (sub.origin) {
|
||||||
SubtitleOrigin.DOWNLOADED_FILE -> {
|
SubtitleOrigin.DOWNLOADED_FILE -> {
|
||||||
if (offlineSourceFactory != null) {
|
if (offlineSourceFactory != null) {
|
||||||
activeSubtitles.add(sub)
|
activeSubtitles.add(sub)
|
||||||
SingleSampleMediaSource.Factory(offlineSourceFactory)
|
SingleSampleMediaSource.Factory(offlineSourceFactory)
|
||||||
.createMediaSource(subConfig, C.TIME_UNSET)
|
.createMediaSource(subConfig, TIME_UNSET)
|
||||||
} else {
|
} else {
|
||||||
null
|
null
|
||||||
}
|
}
|
||||||
|
@ -1156,7 +1152,7 @@ class CS3IPlayer : IPlayer {
|
||||||
if (sub.headers.isNotEmpty())
|
if (sub.headers.isNotEmpty())
|
||||||
this.setDefaultRequestProperties(sub.headers)
|
this.setDefaultRequestProperties(sub.headers)
|
||||||
})
|
})
|
||||||
.createMediaSource(subConfig, C.TIME_UNSET)
|
.createMediaSource(subConfig, TIME_UNSET)
|
||||||
} else {
|
} else {
|
||||||
null
|
null
|
||||||
}
|
}
|
||||||
|
@ -1165,7 +1161,7 @@ class CS3IPlayer : IPlayer {
|
||||||
if (offlineSourceFactory != null) {
|
if (offlineSourceFactory != null) {
|
||||||
activeSubtitles.add(sub)
|
activeSubtitles.add(sub)
|
||||||
SingleSampleMediaSource.Factory(offlineSourceFactory)
|
SingleSampleMediaSource.Factory(offlineSourceFactory)
|
||||||
.createMediaSource(subConfig, C.TIME_UNSET)
|
.createMediaSource(subConfig, TIME_UNSET)
|
||||||
} else {
|
} else {
|
||||||
null
|
null
|
||||||
}
|
}
|
||||||
|
|
|
@ -438,16 +438,17 @@ class GeneratorPlayer : FullScreenPlayer() {
|
||||||
|
|
||||||
private fun addAndSelectSubtitles(subtitleData: SubtitleData) {
|
private fun addAndSelectSubtitles(subtitleData: SubtitleData) {
|
||||||
val ctx = context ?: return
|
val ctx = context ?: return
|
||||||
setSubtitles(subtitleData)
|
|
||||||
|
|
||||||
// this is used instead of observe, because observe is too slow
|
|
||||||
val subs = currentSubs + subtitleData
|
val subs = currentSubs + subtitleData
|
||||||
|
|
||||||
|
// this is used instead of observe(viewModel._currentSubs), because observe is too slow
|
||||||
|
player.setActiveSubtitles(subs)
|
||||||
|
|
||||||
// Save current time as to not reset player to 00:00
|
// Save current time as to not reset player to 00:00
|
||||||
player.saveData()
|
player.saveData()
|
||||||
player.setActiveSubtitles(subs)
|
|
||||||
player.reloadPlayer(ctx)
|
player.reloadPlayer(ctx)
|
||||||
|
|
||||||
|
setSubtitles(subtitleData)
|
||||||
viewModel.addSubtitles(setOf(subtitleData))
|
viewModel.addSubtitles(setOf(subtitleData))
|
||||||
|
|
||||||
selectSourceDialog?.dismissSafe()
|
selectSourceDialog?.dismissSafe()
|
||||||
|
@ -959,7 +960,7 @@ class GeneratorPlayer : FullScreenPlayer() {
|
||||||
subtitles: Set<SubtitleData>, settings: Boolean, downloads: Boolean
|
subtitles: Set<SubtitleData>, settings: Boolean, downloads: Boolean
|
||||||
): SubtitleData? {
|
): SubtitleData? {
|
||||||
val langCode = preferredAutoSelectSubtitles ?: return null
|
val langCode = preferredAutoSelectSubtitles ?: return null
|
||||||
val lang = SubtitleHelper.fromTwoLettersToLanguage(langCode) ?: return null
|
val lang = fromTwoLettersToLanguage(langCode) ?: return null
|
||||||
if (downloads) {
|
if (downloads) {
|
||||||
return subtitles.firstOrNull { sub ->
|
return subtitles.firstOrNull { sub ->
|
||||||
(sub.origin == SubtitleOrigin.DOWNLOADED_FILE && sub.name == context?.getString(
|
(sub.origin == SubtitleOrigin.DOWNLOADED_FILE && sub.name == context?.getString(
|
||||||
|
@ -970,22 +971,11 @@ class GeneratorPlayer : FullScreenPlayer() {
|
||||||
|
|
||||||
sortSubs(subtitles).firstOrNull { sub ->
|
sortSubs(subtitles).firstOrNull { sub ->
|
||||||
val t = sub.name.replace(Regex("[^A-Za-z]"), " ").trim()
|
val t = sub.name.replace(Regex("[^A-Za-z]"), " ").trim()
|
||||||
(settings || (downloads && sub.origin == SubtitleOrigin.DOWNLOADED_FILE)) && t == lang || t.startsWith(
|
(settings) && t == lang || t.startsWith(lang) || t == langCode
|
||||||
"$lang "
|
|
||||||
) || t == langCode
|
|
||||||
}?.let { sub ->
|
}?.let { sub ->
|
||||||
return sub
|
return sub
|
||||||
}
|
}
|
||||||
|
|
||||||
// post check in case both did not catch anything
|
|
||||||
if (downloads) {
|
|
||||||
return subtitles.firstOrNull { sub ->
|
|
||||||
(sub.origin == SubtitleOrigin.DOWNLOADED_FILE || sub.name == context?.getString(
|
|
||||||
R.string.default_subtitles
|
|
||||||
))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1006,14 +996,12 @@ class GeneratorPlayer : FullScreenPlayer() {
|
||||||
getAutoSelectSubtitle(
|
getAutoSelectSubtitle(
|
||||||
currentSubs, settings = true, downloads = false
|
currentSubs, settings = true, downloads = false
|
||||||
)?.let { sub ->
|
)?.let { sub ->
|
||||||
|
|
||||||
if (setSubtitles(sub)) {
|
if (setSubtitles(sub)) {
|
||||||
player.saveData()
|
player.saveData()
|
||||||
player.reloadPlayer(ctx)
|
player.reloadPlayer(ctx)
|
||||||
player.handleEvent(CSPlayerEvent.Play)
|
player.handleEvent(CSPlayerEvent.Play)
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1304,8 +1292,10 @@ class GeneratorPlayer : FullScreenPlayer() {
|
||||||
Log.i("subfilter", "Filtering subtitle")
|
Log.i("subfilter", "Filtering subtitle")
|
||||||
langFilterList.forEach { lang ->
|
langFilterList.forEach { lang ->
|
||||||
Log.i("subfilter", "Lang: $lang")
|
Log.i("subfilter", "Lang: $lang")
|
||||||
setOfSub += set.filter { it.name.contains(lang, ignoreCase = true) }
|
setOfSub += set.filter {
|
||||||
.toMutableSet()
|
it.name.contains(lang, ignoreCase = true) ||
|
||||||
|
it.origin != SubtitleOrigin.URL
|
||||||
|
}
|
||||||
}
|
}
|
||||||
currentSubs = setOfSub
|
currentSubs = setOfSub
|
||||||
} else {
|
} else {
|
||||||
|
@ -1313,7 +1303,13 @@ class GeneratorPlayer : FullScreenPlayer() {
|
||||||
}
|
}
|
||||||
player.setActiveSubtitles(set)
|
player.setActiveSubtitles(set)
|
||||||
|
|
||||||
|
// If the file is downloaded then do not select auto select the subtitles
|
||||||
|
// Downloaded subtitles cannot be selected immediately after loading since
|
||||||
|
// player.getCurrentPreferredSubtitle() cannot fetch data from non-loaded subtitles
|
||||||
|
// Resulting in unselecting the downloaded subtitle
|
||||||
|
if (set.lastOrNull()?.origin != SubtitleOrigin.DOWNLOADED_FILE) {
|
||||||
autoSelectSubtitles()
|
autoSelectSubtitles()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
|
@ -113,9 +113,10 @@ class PlayerGeneratorViewModel : ViewModel() {
|
||||||
// Do not post if there's nothing new
|
// Do not post if there's nothing new
|
||||||
// Posting will refresh subtitles which will in turn
|
// Posting will refresh subtitles which will in turn
|
||||||
// make the subs to english if previously unselected
|
// make the subs to english if previously unselected
|
||||||
if (allSubs != currentSubs)
|
if (allSubs != currentSubs) {
|
||||||
_currentSubs.postValue(allSubs)
|
_currentSubs.postValue(allSubs)
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private var currentJob: Job? = null
|
private var currentJob: Job? = null
|
||||||
private var currentStampJob: Job? = null
|
private var currentStampJob: Job? = null
|
||||||
|
@ -164,9 +165,10 @@ class PlayerGeneratorViewModel : ViewModel() {
|
||||||
}
|
}
|
||||||
|
|
||||||
_loadingLinks.postValue(loadingState)
|
_loadingLinks.postValue(loadingState)
|
||||||
|
|
||||||
_currentLinks.postValue(currentLinks)
|
_currentLinks.postValue(currentLinks)
|
||||||
_currentSubs.postValue(currentSubs)
|
_currentSubs.postValue(
|
||||||
|
currentSubs.union(_currentSubs.value ?: emptySet())
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -28,7 +28,7 @@ enum class SubtitleOrigin {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param name To be displayed in the player
|
* @param name To be displayed in the player
|
||||||
* @param url Url for the subtitle, when EMBEDDED_IN_VIDEO this variable is used as the real backend language
|
* @param url Url for the subtitle, when EMBEDDED_IN_VIDEO this variable is used as the real backend id
|
||||||
* @param headers if empty it will use the base onlineDataSource headers else only the specified headers
|
* @param headers if empty it will use the base onlineDataSource headers else only the specified headers
|
||||||
* */
|
* */
|
||||||
data class SubtitleData(
|
data class SubtitleData(
|
||||||
|
@ -37,7 +37,13 @@ data class SubtitleData(
|
||||||
val origin: SubtitleOrigin,
|
val origin: SubtitleOrigin,
|
||||||
val mimeType: String,
|
val mimeType: String,
|
||||||
val headers: Map<String, String>
|
val headers: Map<String, String>
|
||||||
)
|
) {
|
||||||
|
/** Internal ID for exoplayer, unique for each link*/
|
||||||
|
fun getId(): String {
|
||||||
|
return if (origin == SubtitleOrigin.EMBEDDED_IN_VIDEO) url
|
||||||
|
else "$url|$name"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
class PlayerSubtitleHelper {
|
class PlayerSubtitleHelper {
|
||||||
private var activeSubtitles: Set<SubtitleData> = emptySet()
|
private var activeSubtitles: Set<SubtitleData> = emptySet()
|
||||||
|
|
Loading…
Reference in a new issue