Merge branch 'recloudstream:master' into initial-multi-delete

This commit is contained in:
Luna712 2024-07-25 12:50:12 -06:00 committed by GitHub
commit 7214c9ff14
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
86 changed files with 922 additions and 269 deletions

View file

@ -60,8 +60,8 @@ android {
minSdk = 21 minSdk = 21
targetSdk = 33 /* Android 14 is Fu*ked targetSdk = 33 /* Android 14 is Fu*ked
^ https://developer.android.com/about/versions/14/behavior-changes-14#safer-dynamic-code-loading*/ ^ https://developer.android.com/about/versions/14/behavior-changes-14#safer-dynamic-code-loading*/
versionCode = 63 versionCode = 64
versionName = "4.3.2" versionName = "4.4.0"
resValue("string", "app_version", "${defaultConfig.versionName}${versionNameSuffix ?: ""}") resValue("string", "app_version", "${defaultConfig.versionName}${versionNameSuffix ?: ""}")
resValue("string", "commit_hash", "git rev-parse --short HEAD".execute() ?: "") resValue("string", "commit_hash", "git rev-parse --short HEAD".execute() ?: "")
@ -200,7 +200,7 @@ dependencies {
// PlayBack // PlayBack
implementation("com.jaredrummler:colorpicker:1.1.0") // Subtitle Color Picker implementation("com.jaredrummler:colorpicker:1.1.0") // Subtitle Color Picker
implementation("com.github.recloudstream:media-ffmpeg:1.1.0") // Custom FF-MPEG Lib for Audio Codecs implementation("com.github.recloudstream:media-ffmpeg:1.1.0") // Custom FF-MPEG Lib for Audio Codecs
implementation("com.github.teamnewpipe:NewPipeExtractor:592f159") /* For Trailers implementation("com.github.teamnewpipe:NewPipeExtractor:2d36945") /* For Trailers
^ Update to Latest Commits if Trailers Misbehave, github.com/TeamNewPipe/NewPipeExtractor/commits/dev */ ^ Update to Latest Commits if Trailers Misbehave, github.com/TeamNewPipe/NewPipeExtractor/commits/dev */
implementation("com.github.albfernandez:juniversalchardet:2.5.0") // Subtitle Decoding implementation("com.github.albfernandez:juniversalchardet:2.5.0") // Subtitle Decoding

View file

@ -154,7 +154,7 @@ class ExampleInstrumentedTest {
fun providerCorrectHomepage() { fun providerCorrectHomepage() {
runBlocking { runBlocking {
getAllProviders().toList().amap { api -> getAllProviders().toList().amap { api ->
TestingUtils.testHomepage(api, ::println) TestingUtils.testHomepage(api, TestingUtils.Logger())
} }
} }
println("Done providerCorrectHomepage") println("Done providerCorrectHomepage")
@ -166,7 +166,6 @@ class ExampleInstrumentedTest {
TestingUtils.getDeferredProviderTests( TestingUtils.getDeferredProviderTests(
this, this,
getAllProviders(), getAllProviders(),
::println
) { _, _ -> } ) { _, _ -> }
} }
} }

View file

@ -107,6 +107,7 @@ import com.lagradost.cloudstream3.ui.result.START_ACTION_RESUME_LATEST
import com.lagradost.cloudstream3.ui.result.SyncViewModel import com.lagradost.cloudstream3.ui.result.SyncViewModel
import com.lagradost.cloudstream3.ui.result.setImage import com.lagradost.cloudstream3.ui.result.setImage
import com.lagradost.cloudstream3.ui.result.setText import com.lagradost.cloudstream3.ui.result.setText
import com.lagradost.cloudstream3.ui.result.setTextHtml
import com.lagradost.cloudstream3.ui.result.txt import com.lagradost.cloudstream3.ui.result.txt
import com.lagradost.cloudstream3.ui.search.SearchFragment import com.lagradost.cloudstream3.ui.search.SearchFragment
import com.lagradost.cloudstream3.ui.search.SearchResultBuilder import com.lagradost.cloudstream3.ui.search.SearchResultBuilder
@ -1402,7 +1403,7 @@ class MainActivity : AppCompatActivity(), ColorPickerDialogListener, BiometricCa
resultviewPreviewMetaDuration.setText(d.durationText) resultviewPreviewMetaDuration.setText(d.durationText)
resultviewPreviewMetaRating.setText(d.ratingText) resultviewPreviewMetaRating.setText(d.ratingText)
resultviewPreviewDescription.setText(d.plotText) resultviewPreviewDescription.setTextHtml(d.plotText)
resultviewPreviewPoster.setImage( resultviewPreviewPoster.setImage(
d.posterImage ?: d.posterBackgroundImage d.posterImage ?: d.posterBackgroundImage
) )

View file

@ -236,6 +236,7 @@ open class TraktProvider : MainAPI() {
posterUrl = fixPath(episode.images?.screenshot?.firstOrNull()), posterUrl = fixPath(episode.images?.screenshot?.firstOrNull()),
rating = episode.rating?.times(10)?.roundToInt(), rating = episode.rating?.times(10)?.roundToInt(),
description = episode.overview, description = episode.overview,
runTime = episode.runtime
).apply { ).apply {
this.addDate(episode.firstAired, "yyyy-MM-dd'T'HH:mm:ss.SSSXXX") this.addDate(episode.firstAired, "yyyy-MM-dd'T'HH:mm:ss.SSSXXX")
if (nextAir == null && this.date != null && this.date!! > unixTimeMS && this.season != 0) { if (nextAir == null && this.date != null && this.date!! > unixTimeMS && this.season != 0) {

View file

@ -912,7 +912,11 @@ class CS3IPlayer : IPlayer {
} }
CSPlayerEvent.SeekForward -> seekTime(seekActionTime, source) CSPlayerEvent.SeekForward -> seekTime(seekActionTime, source)
CSPlayerEvent.SeekBack -> seekTime(-seekActionTime, source) CSPlayerEvent.SeekBack -> seekTime(-seekActionTime, source)
CSPlayerEvent.Restart -> seekTo(0, source)
CSPlayerEvent.NextEpisode -> event( CSPlayerEvent.NextEpisode -> event(
EpisodeSeekEvent( EpisodeSeekEvent(
offset = 1, offset = 1,

View file

@ -14,20 +14,29 @@ import android.os.Bundle
import android.provider.Settings import android.provider.Settings
import android.text.Editable import android.text.Editable
import android.text.format.DateUtils import android.text.format.DateUtils
import android.view.* import android.view.KeyEvent
import android.view.LayoutInflater
import android.view.MotionEvent
import android.view.Surface
import android.view.View
import android.view.ViewGroup
import android.view.WindowManager
import android.view.WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_SHORT_EDGES import android.view.WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_SHORT_EDGES
import android.view.animation.AlphaAnimation import android.view.animation.AlphaAnimation
import android.view.animation.Animation import android.view.animation.Animation
import android.view.animation.AnimationUtils import android.view.animation.AnimationUtils
import android.widget.LinearLayout
import androidx.appcompat.app.AlertDialog import androidx.appcompat.app.AlertDialog
import androidx.core.graphics.blue import androidx.core.graphics.blue
import androidx.core.graphics.green import androidx.core.graphics.green
import androidx.core.graphics.red import androidx.core.graphics.red
import androidx.core.view.children
import androidx.core.view.isGone import androidx.core.view.isGone
import androidx.core.view.isInvisible import androidx.core.view.isInvisible
import androidx.core.view.isVisible import androidx.core.view.isVisible
import androidx.core.widget.doOnTextChanged import androidx.core.widget.doOnTextChanged
import androidx.preference.PreferenceManager import androidx.preference.PreferenceManager
import com.google.android.material.button.MaterialButton
import com.lagradost.cloudstream3.CommonActivity.keyEventListener import com.lagradost.cloudstream3.CommonActivity.keyEventListener
import com.lagradost.cloudstream3.CommonActivity.playerEventListener import com.lagradost.cloudstream3.CommonActivity.playerEventListener
import com.lagradost.cloudstream3.CommonActivity.screenHeight import com.lagradost.cloudstream3.CommonActivity.screenHeight
@ -58,7 +67,11 @@ import com.lagradost.cloudstream3.utils.UIHelper.showSystemUI
import com.lagradost.cloudstream3.utils.UIHelper.toPx import com.lagradost.cloudstream3.utils.UIHelper.toPx
import com.lagradost.cloudstream3.utils.UserPreferenceDelegate import com.lagradost.cloudstream3.utils.UserPreferenceDelegate
import com.lagradost.cloudstream3.utils.Vector2 import com.lagradost.cloudstream3.utils.Vector2
import kotlin.math.* import kotlin.math.abs
import kotlin.math.ceil
import kotlin.math.max
import kotlin.math.min
import kotlin.math.round
const val MINIMUM_SEEK_TIME = 7000L // when swipe seeking const val MINIMUM_SEEK_TIME = 7000L // when swipe seeking
const val MINIMUM_VERTICAL_SWIPE = 2.0f // in percentage const val MINIMUM_VERTICAL_SWIPE = 2.0f // in percentage
@ -77,7 +90,8 @@ open class FullScreenPlayer : AbstractPlayerFragment() {
protected open var isFullScreenPlayer = true protected open var isFullScreenPlayer = true
protected var playerBinding: PlayerCustomLayoutBinding? = null protected var playerBinding: PlayerCustomLayoutBinding? = null
private var durationMode : Boolean by UserPreferenceDelegate("duration_mode", false) private var durationMode: Boolean by UserPreferenceDelegate("duration_mode", false)
// state of player UI // state of player UI
protected var isShowing = false protected var isShowing = false
protected var isLocked = false protected var isLocked = false
@ -109,6 +123,7 @@ open class FullScreenPlayer : AbstractPlayerFragment() {
protected var doubleTapPauseEnabled = true protected var doubleTapPauseEnabled = true
protected var playerRotateEnabled = false protected var playerRotateEnabled = false
protected var autoPlayerRotateEnabled = false protected var autoPlayerRotateEnabled = false
private var hideControlsNames = false
protected var subtitleDelay protected var subtitleDelay
set(value) = try { set(value) = try {
@ -243,7 +258,6 @@ open class FullScreenPlayer : AbstractPlayerFragment() {
val playerSourceMove = if (isShowing) 0f else -50.toPx.toFloat() val playerSourceMove = if (isShowing) 0f else -50.toPx.toFloat()
playerBinding?.apply { playerBinding?.apply {
playerOpenSource.let { playerOpenSource.let {
ObjectAnimator.ofFloat(it, "translationY", playerSourceMove).apply { ObjectAnimator.ofFloat(it, "translationY", playerSourceMove).apply {
@ -284,7 +298,7 @@ open class FullScreenPlayer : AbstractPlayerFragment() {
player.getCurrentPreferredSubtitle() == null player.getCurrentPreferredSubtitle() == null
} }
private fun restoreOrientationWithSensor(activity: Activity){ private fun restoreOrientationWithSensor(activity: Activity) {
val currentOrientation = activity.resources.configuration.orientation val currentOrientation = activity.resources.configuration.orientation
var orientation = 0 var orientation = 0
when (currentOrientation) { when (currentOrientation) {
@ -300,7 +314,7 @@ open class FullScreenPlayer : AbstractPlayerFragment() {
activity.requestedOrientation = orientation activity.requestedOrientation = orientation
} }
private fun toggleOrientationWithSensor(activity: Activity){ private fun toggleOrientationWithSensor(activity: Activity) {
val currentOrientation = activity.resources.configuration.orientation val currentOrientation = activity.resources.configuration.orientation
var orientation = 0 var orientation = 0
when (currentOrientation) { when (currentOrientation) {
@ -345,12 +359,11 @@ open class FullScreenPlayer : AbstractPlayerFragment() {
private fun updateOrientation(ignoreDynamicOrientation: Boolean = false) { private fun updateOrientation(ignoreDynamicOrientation: Boolean = false) {
activity?.apply { activity?.apply {
if(lockRotation) { if (lockRotation) {
if(isLocked) { if (isLocked) {
lockOrientation(this) lockOrientation(this)
} } else {
else { if (ignoreDynamicOrientation) {
if(ignoreDynamicOrientation){
// restore when lock is disabled // restore when lock is disabled
restoreOrientationWithSensor(this) restoreOrientationWithSensor(this)
} else { } else {
@ -728,6 +741,15 @@ open class FullScreenPlayer : AbstractPlayerFragment() {
private var currentTapIndex = 0 private var currentTapIndex = 0
protected fun autoHide() { protected fun autoHide() {
currentTapIndex++ currentTapIndex++
delayHide()
}
override fun playerStatusChanged() {
super.playerStatusChanged()
delayHide()
}
private fun delayHide() {
val index = currentTapIndex val index = currentTapIndex
playerBinding?.playerHolder?.postDelayed({ playerBinding?.playerHolder?.postDelayed({
if (!isCurrentTouchValid && isShowing && index == currentTapIndex && player.getIsPlaying()) { if (!isCurrentTouchValid && isShowing && index == currentTapIndex && player.getIsPlaying()) {
@ -949,7 +971,10 @@ open class FullScreenPlayer : AbstractPlayerFragment() {
} }
else -> { else -> {
player.handleEvent(CSPlayerEvent.PlayPauseToggle, PlayerEventSource.UI) player.handleEvent(
CSPlayerEvent.PlayPauseToggle,
PlayerEventSource.UI
)
} }
} }
} else if (doubleTapEnabled && isFullScreenPlayer) { } else if (doubleTapEnabled && isFullScreenPlayer) {
@ -1225,6 +1250,7 @@ open class FullScreenPlayer : AbstractPlayerFragment() {
// if nothing has loaded these buttons should not be visible // if nothing has loaded these buttons should not be visible
playerBinding?.apply { playerBinding?.apply {
playerSkipEpisode.isVisible = false playerSkipEpisode.isVisible = false
playerGoForward.isVisible = false
playerTracksBtt.isVisible = false playerTracksBtt.isVisible = false
playerSkipOp.isVisible = false playerSkipOp.isVisible = false
shadowOverlay.isVisible = false shadowOverlay.isVisible = false
@ -1298,6 +1324,10 @@ open class FullScreenPlayer : AbstractPlayerFragment() {
player.handleEvent(CSPlayerEvent.SeekBack) player.handleEvent(CSPlayerEvent.SeekBack)
} }
PlayerEventType.Restart -> {
player.handleEvent(CSPlayerEvent.Restart)
}
PlayerEventType.ToggleMute -> { PlayerEventType.ToggleMute -> {
player.handleEvent(CSPlayerEvent.ToggleMute) player.handleEvent(CSPlayerEvent.ToggleMute)
} }
@ -1393,6 +1423,8 @@ open class FullScreenPlayer : AbstractPlayerFragment() {
false false
) )
hideControlsNames = settingsManager.getBoolean(ctx.getString(R.string.hide_player_control_names_key), false)
val profiles = QualityDataHelper.getProfiles() val profiles = QualityDataHelper.getProfiles()
val type = if (ctx.isUsingMobileData()) val type = if (ctx.isUsingMobileData())
QualityDataHelper.QualityProfileType.Data QualityDataHelper.QualityProfileType.Data
@ -1413,12 +1445,34 @@ open class FullScreenPlayer : AbstractPlayerFragment() {
playerSpeedBtt.isVisible = playBackSpeedEnabled playerSpeedBtt.isVisible = playBackSpeedEnabled
playerResizeBtt.isVisible = playerResizeEnabled playerResizeBtt.isVisible = playerResizeEnabled
playerRotateBtt.isVisible = playerRotateEnabled playerRotateBtt.isVisible = playerRotateEnabled
if (hideControlsNames) {
hideControlsNames()
}
} }
} catch (e: Exception) { } catch (e: Exception) {
logError(e) logError(e)
} }
playerBinding?.apply { playerBinding?.apply {
if (isLayout(TV or EMULATOR)) {
mapOf(
playerGoBack to playerGoBackText,
playerRestart to playerRestartText,
playerGoForward to playerGoForwardText
).forEach { (button, text) ->
button.setOnFocusChangeListener { _, hasFocus ->
if (!hasFocus) {
text.isSelected = false
text.isVisible = false
return@setOnFocusChangeListener
}
text.isSelected = true
text.isVisible = true
}
}
}
playerPausePlay.setOnClickListener { playerPausePlay.setOnClickListener {
autoHide() autoHide()
player.handleEvent(CSPlayerEvent.PlayPauseToggle) player.handleEvent(CSPlayerEvent.PlayPauseToggle)
@ -1462,6 +1516,16 @@ open class FullScreenPlayer : AbstractPlayerFragment() {
player.handleEvent(CSPlayerEvent.NextEpisode) player.handleEvent(CSPlayerEvent.NextEpisode)
} }
playerGoForward.setOnClickListener {
autoHide()
player.handleEvent(CSPlayerEvent.NextEpisode)
}
playerRestart.setOnClickListener {
autoHide()
player.handleEvent(CSPlayerEvent.Restart)
}
playerLock.setOnClickListener { playerLock.setOnClickListener {
autoHide() autoHide()
toggleLock() toggleLock()
@ -1536,6 +1600,22 @@ open class FullScreenPlayer : AbstractPlayerFragment() {
} }
} }
private fun PlayerCustomLayoutBinding.hideControlsNames() {
fun iterate(layout: LinearLayout) {
layout.children.forEach {
if (it is MaterialButton) {
it.textSize = 0f
it.iconPadding = 0
it.iconGravity = MaterialButton.ICON_GRAVITY_TEXT_START
it.setPadding(0,0,0,0)
} else if (it is LinearLayout) {
iterate(it)
}
}
}
iterate(playerLockHolder.parent as LinearLayout)
}
override fun playerDimensionsLoaded(width: Int, height: Int) { override fun playerDimensionsLoaded(width: Int, height: Int) {
isVerticalOrientation = height > width isVerticalOrientation = height > width
updateOrientation() updateOrientation()
@ -1555,7 +1635,7 @@ open class FullScreenPlayer : AbstractPlayerFragment() {
private fun setRemainingTimeCounter(showRemaining: Boolean) { private fun setRemainingTimeCounter(showRemaining: Boolean) {
durationMode = showRemaining durationMode = showRemaining
playerBinding?.exoDuration?.isInvisible= showRemaining playerBinding?.exoDuration?.isInvisible = showRemaining
playerBinding?.timeLeft?.isVisible = showRemaining playerBinding?.timeLeft?.isVisible = showRemaining
} }

View file

@ -44,6 +44,7 @@ import com.lagradost.cloudstream3.ui.player.source_priority.QualityDataHelper
import com.lagradost.cloudstream3.ui.player.source_priority.QualityProfileDialog import com.lagradost.cloudstream3.ui.player.source_priority.QualityProfileDialog
import com.lagradost.cloudstream3.ui.result.* import com.lagradost.cloudstream3.ui.result.*
import com.lagradost.cloudstream3.ui.settings.Globals.EMULATOR import com.lagradost.cloudstream3.ui.settings.Globals.EMULATOR
import com.lagradost.cloudstream3.ui.settings.Globals.PHONE
import com.lagradost.cloudstream3.ui.settings.Globals.TV import com.lagradost.cloudstream3.ui.settings.Globals.TV
import com.lagradost.cloudstream3.ui.settings.Globals.isLayout import com.lagradost.cloudstream3.ui.settings.Globals.isLayout
import com.lagradost.cloudstream3.ui.subtitles.SUBTITLE_AUTO_SELECT_KEY import com.lagradost.cloudstream3.ui.subtitles.SUBTITLE_AUTO_SELECT_KEY
@ -158,6 +159,7 @@ class GeneratorPlayer : FullScreenPlayer() {
} }
override fun playerStatusChanged() { override fun playerStatusChanged() {
super.playerStatusChanged()
if (player.getIsPlaying()) { if (player.getIsPlaying()) {
viewModel.forceClearCache = false viewModel.forceClearCache = false
} }
@ -1096,8 +1098,15 @@ class GeneratorPlayer : FullScreenPlayer() {
} }
playerBinding?.playerSkipOp?.isVisible = isOpVisible playerBinding?.playerSkipOp?.isVisible = isOpVisible
playerBinding?.playerSkipEpisode?.isVisible =
!isOpVisible && viewModel.hasNextEpisode() == true when {
isLayout(PHONE) ->
playerBinding?.playerSkipEpisode?.isVisible =
!isOpVisible && viewModel.hasNextEpisode() == true
else ->
playerBinding?.playerGoForward?.isVisible = viewModel.hasNextEpisode() == true
}
if (percentage >= PRELOAD_NEXT_EPISODE_PERCENTAGE) { if (percentage >= PRELOAD_NEXT_EPISODE_PERCENTAGE) {
viewModel.preLoadNextLinks() viewModel.preLoadNextLinks()
@ -1253,7 +1262,7 @@ class GeneratorPlayer : FullScreenPlayer() {
fun setPlayerDimen(widthHeight: Pair<Int, Int>?) { fun setPlayerDimen(widthHeight: Pair<Int, Int>?) {
val extra = if (widthHeight != null) { val extra = if (widthHeight != null) {
val (width, height) = widthHeight val (width, height) = widthHeight
"${width}x${height}" "- ${width}x${height}"
} else { } else {
"" ""
} }
@ -1264,7 +1273,7 @@ class GeneratorPlayer : FullScreenPlayer() {
0 -> "" 0 -> ""
1 -> extra 1 -> extra
2 -> source 2 -> source
3 -> "$source - $extra" 3 -> "$source $extra"
else -> "" else -> ""
} }
playerBinding?.playerVideoTitleRez?.apply { playerBinding?.playerVideoTitleRez?.apply {
@ -1289,7 +1298,8 @@ class GeneratorPlayer : FullScreenPlayer() {
inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle? inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?
): View? { ): View? {
// this is used instead of layout-television to follow the settings and some TV devices are not classified as TV for some reason // this is used instead of layout-television to follow the settings and some TV devices are not classified as TV for some reason
layout = if (isLayout(TV or EMULATOR)) R.layout.fragment_player_tv else R.layout.fragment_player layout =
if (isLayout(TV or EMULATOR)) R.layout.fragment_player_tv else R.layout.fragment_player
viewModel = ViewModelProvider(this)[PlayerGeneratorViewModel::class.java] viewModel = ViewModelProvider(this)[PlayerGeneratorViewModel::class.java]
sync = ViewModelProvider(this)[SyncViewModel::class.java] sync = ViewModelProvider(this)[SyncViewModel::class.java]

View file

@ -26,6 +26,7 @@ enum class PlayerEventType(val value: Int) {
Resize(13), Resize(13),
SearchSubtitlesOnline(14), SearchSubtitlesOnline(14),
SkipOp(15), SkipOp(15),
Restart(16),
} }
enum class CSPlayerEvent(val value: Int) { enum class CSPlayerEvent(val value: Int) {
@ -39,6 +40,7 @@ enum class CSPlayerEvent(val value: Int) {
PrevEpisode(6), PrevEpisode(6),
PlayPauseToggle(7), PlayPauseToggle(7),
ToggleMute(8), ToggleMute(8),
Restart(9),
} }
enum class CSPlayerLoading { enum class CSPlayerLoading {

View file

@ -27,7 +27,8 @@ import com.lagradost.cloudstream3.utils.UIHelper.toPx
import com.lagradost.cloudstream3.utils.VideoDownloadHelper import com.lagradost.cloudstream3.utils.VideoDownloadHelper
import java.text.DateFormat import java.text.DateFormat
import java.text.SimpleDateFormat import java.text.SimpleDateFormat
import java.util.* import java.util.Date
import java.util.Locale
const val ACTION_PLAY_EPISODE_IN_PLAYER = 1 const val ACTION_PLAY_EPISODE_IN_PLAYER = 1
const val ACTION_PLAY_EPISODE_IN_VLC_PLAYER = 2 const val ACTION_PLAY_EPISODE_IN_VLC_PLAYER = 2
@ -58,6 +59,7 @@ const val ACTION_MARK_AS_WATCHED = 18
const val ACTION_FCAST = 19 const val ACTION_FCAST = 19
const val TV_EP_SIZE = 400 const val TV_EP_SIZE = 400
data class EpisodeClickEvent(val action: Int, val data: ResultEpisode) data class EpisodeClickEvent(val action: Int, val data: ResultEpisode)
class EpisodeAdapter( class EpisodeAdapter(
@ -274,7 +276,10 @@ class EpisodeAdapter(
episodeDate.setText( episodeDate.setText(
txt( txt(
R.string.episode_upcoming_format, R.string.episode_upcoming_format,
secondsToReadable(card.airDate.minus(unixTimeMS).div(1000).toInt(), "") secondsToReadable(
card.airDate.minus(unixTimeMS).div(1000).toInt(),
""
)
) )
) )
} else { } else {
@ -292,6 +297,12 @@ class EpisodeAdapter(
episodeDate.isVisible = false episodeDate.isVisible = false
} }
episodeRuntime.setText(
txt(
card.runTime?.times(60L)?.toInt()?.let { secondsToReadable(it, "") }
)
)
if (isLayout(EMULATOR or PHONE)) { if (isLayout(EMULATOR or PHONE)) {
episodePoster.setOnClickListener { episodePoster.setOnClickListener {
clickCallback.invoke(EpisodeClickEvent(ACTION_CLICK_DEFAULT, card)) clickCallback.invoke(EpisodeClickEvent(ACTION_CLICK_DEFAULT, card))

View file

@ -51,6 +51,7 @@ data class ResultEpisode(
/** Sum of all previous season episode counts + episode */ /** Sum of all previous season episode counts + episode */
val totalEpisodeIndex: Int? = null, val totalEpisodeIndex: Int? = null,
val airDate: Long? = null, val airDate: Long? = null,
val runTime: Int? = null,
) )
fun ResultEpisode.getRealPosition(): Long { fun ResultEpisode.getRealPosition(): Long {
@ -87,6 +88,7 @@ fun buildResultEpisode(
parentId: Int, parentId: Int,
totalEpisodeIndex: Int? = null, totalEpisodeIndex: Int? = null,
airDate: Long? = null, airDate: Long? = null,
runTime: Int? = null,
): ResultEpisode { ): ResultEpisode {
val posDur = getViewPos(id) val posDur = getViewPos(id)
val videoWatchState = getVideoWatchState(id) ?: VideoWatchState.None val videoWatchState = getVideoWatchState(id) ?: VideoWatchState.None
@ -111,6 +113,7 @@ fun buildResultEpisode(
videoWatchState, videoWatchState,
totalEpisodeIndex, totalEpisodeIndex,
airDate, airDate,
runTime,
) )
} }

View file

@ -2371,7 +2371,8 @@ class ResultViewModel2 : ViewModel() {
loadResponse.type, loadResponse.type,
mainId, mainId,
totalIndex, totalIndex,
airDate = i.date airDate = i.date,
runTime = i.runTime,
) )
val season = eps.seasonIndex ?: 0 val season = eps.seasonIndex ?: 0
@ -2426,7 +2427,8 @@ class ResultViewModel2 : ViewModel() {
loadResponse.type, loadResponse.type,
mainId, mainId,
totalIndex, totalIndex,
airDate = episode.date airDate = episode.date,
runTime = episode.runTime,
) )
val season = ep.seasonIndex ?: 0 val season = ep.seasonIndex ?: 0

View file

@ -87,10 +87,6 @@ class SettingsPlayer : PreferenceFragmentCompat() {
return@setOnPreferenceClickListener true return@setOnPreferenceClickListener true
} }
/*(getPref(R.string.double_tap_seek_time_key) as? SeekBarPreference?)?.let {
}*/
getPref(R.string.prefer_limit_title_rez_key)?.setOnPreferenceClickListener { getPref(R.string.prefer_limit_title_rez_key)?.setOnPreferenceClickListener {
val prefNames = resources.getStringArray(R.array.limit_title_rez_pref_names) val prefNames = resources.getStringArray(R.array.limit_title_rez_pref_names)
val prefValues = resources.getIntArray(R.array.limit_title_rez_pref_values) val prefValues = resources.getIntArray(R.array.limit_title_rez_pref_values)
@ -109,6 +105,8 @@ class SettingsPlayer : PreferenceFragmentCompat() {
return@setOnPreferenceClickListener true return@setOnPreferenceClickListener true
} }
getPref(R.string.hide_player_control_names_key)?.hideOn(TV)
getPref(R.string.quality_pref_key)?.setOnPreferenceClickListener { getPref(R.string.quality_pref_key)?.setOnPreferenceClickListener {
val prefValues = Qualities.values().map { it.value }.reversed().toMutableList() val prefValues = Qualities.values().map { it.value }.reversed().toMutableList()
prefValues.remove(Qualities.Unknown.value) prefValues.remove(Qualities.Unknown.value)

View file

@ -67,8 +67,6 @@ object BackupUtils {
OPEN_SUBTITLES_USER_KEY, OPEN_SUBTITLES_USER_KEY,
SUBDL_SUBTITLES_USER_KEY, SUBDL_SUBTITLES_USER_KEY,
DOWNLOAD_EPISODE_CACHE,
"biometric_key", // can lock down users if backup is shared on a incompatible device "biometric_key", // can lock down users if backup is shared on a incompatible device
"nginx_user", // Nginx user key "nginx_user", // Nginx user key
"download_path_key" // No access rights after restore data from backup "download_path_key" // No access rights after restore data from backup
@ -266,4 +264,4 @@ object BackupUtils {
} }
editor.apply() editor.apply()
} }
} }

View file

@ -0,0 +1,9 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="960"
android:viewportHeight="960">
<path
android:pathData="M160,800v-320h160v320L160,800ZM400,800v-640h160v640L400,800ZM640,800v-440h160v440L640,800Z"
android:fillColor="#5f6368"/>
</vector>

View file

@ -0,0 +1,9 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24"
android:viewportHeight="24">
<path
android:pathData="M12,5V2.21c0,-0.45 -0.54,-0.67 -0.85,-0.35l-3.8,3.79c-0.2,0.2 -0.2,0.51 0,0.71l3.79,3.79c0.32,0.31 0.86,0.09 0.86,-0.36V7c3.73,0 6.68,3.42 5.86,7.29 -0.47,2.27 -2.31,4.1 -4.57,4.57 -3.57,0.75 -6.75,-1.7 -7.23,-5.01 -0.07,-0.48 -0.49,-0.85 -0.98,-0.85 -0.6,0 -1.08,0.53 -1,1.13 0.62,4.39 4.8,7.64 9.53,6.72 3.12,-0.61 5.63,-3.12 6.24,-6.24C20.84,9.48 16.94,5 12,5z"
android:fillColor="#e8eaed"/>
</vector>

View file

@ -0,0 +1,9 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="960"
android:viewportHeight="960">
<path
android:pathData="M440,838q-121,-15 -200.5,-105.5T160,520q0,-66 26,-126.5T260,288l57,57q-38,34 -57.5,79T240,520q0,88 56,155.5T440,758v80ZM520,838v-80q87,-16 143.5,-83T720,520q0,-100 -70,-170t-170,-70h-3l44,44 -56,56 -140,-140 140,-140 56,56 -44,44h3q134,0 227,93t93,227q0,121 -79.5,211.5T520,838Z"
android:fillColor="#e8eaed"/>
</vector>

View file

@ -0,0 +1,10 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24"
android:viewportHeight="24">
<path
android:pathData="M2.775,21.225 L15.844,12 2.775,2.775ZM18.15,2.775v18.45h3.075V2.775Z"
android:strokeWidth="1.5375"
android:fillColor="#e8eaed"/>
</vector>

View file

@ -0,0 +1,9 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24"
android:viewportHeight="24">
<path
android:pathData="M7.58,16.89l5.77,-4.07c0.56,-0.4 0.56,-1.24 0,-1.63L7.58,7.11C6.91,6.65 6,7.12 6,7.93v8.14c0,0.81 0.91,1.28 1.58,0.82zM16,7v10c0,0.55 0.45,1 1,1s1,-0.45 1,-1V7c0,-0.55 -0.45,-1 -1,-1s-1,0.45 -1,1z"
android:fillColor="#e8eaed"/>
</vector>

View file

@ -68,7 +68,9 @@
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_margin="5dp" android:layout_margin="5dp"
app:layout_constraintStart_toStartOf="parent" app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"> app:layout_constraintTop_toTopOf="parent"
android:visibility="gone"
tools:visibility="visible">
<ImageView <ImageView
android:layout_width="30dp" android:layout_width="30dp"
@ -82,12 +84,11 @@
android:id="@+id/player_loading_go_back" android:id="@+id/player_loading_go_back"
android:layout_width="70dp" android:layout_width="70dp"
android:layout_height="70dp" android:layout_height="70dp"
android:layout_gravity="center" android:layout_gravity="center"
android:background="@drawable/video_tap_button_always_white" android:background="@drawable/video_tap_button_always_white"
android:clickable="true" android:clickable="false"
android:focusable="true" android:focusable="false"
android:focusableInTouchMode="true" android:focusableInTouchMode="false"
android:nextFocusRight="@id/overlay_loading_skip_button" android:nextFocusRight="@id/overlay_loading_skip_button"
android:nextFocusDown="@id/overlay_loading_skip_button" /> android:nextFocusDown="@id/overlay_loading_skip_button" />
</FrameLayout> </FrameLayout>

View file

@ -172,27 +172,108 @@
android:id="@+id/player_go_back_holder" android:id="@+id/player_go_back_holder"
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_margin="5dp" android:layout_margin="20dp"
app:layout_constraintStart_toStartOf="parent" app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"> app:layout_constraintTop_toTopOf="parent">
<ImageView <LinearLayout
android:layout_width="30dp" android:layout_width="wrap_content"
android:layout_height="30dp" android:layout_height="wrap_content"
android:layout_gravity="center" android:layout_marginEnd="10dp"
android:contentDescription="@string/go_back_img_des" android:id="@+id/player_go_back_root"
android:src="@drawable/ic_baseline_arrow_back_24" android:orientation="vertical">
app:tint="@android:color/white" />
<ImageView
android:id="@+id/player_go_back"
android:layout_width="30dp"
android:layout_height="30dp"
android:layout_gravity="center"
android:src="@drawable/ic_baseline_arrow_back_24"
app:tint="@android:color/white"
android:background="@drawable/video_tap_button_always_white"
android:clickable="true"
android:contentDescription="@string/go_back_img_des"
android:focusable="true"
android:nextFocusRight="@id/player_restart"
android:nextFocusLeft="@id/player_go_back"
android:nextFocusDown="@id/player_pause_play"
android:nextFocusUp="@id/player_go_back"
android:tag="@string/tv_no_focus_tag" />
<TextView
android:id="@+id/player_go_back_text"
style="@style/ResultMarqueeButtonText"
android:text="@string/go_back_img_des"
android:visibility="gone"/>
</LinearLayout>
<LinearLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="40dp"
android:layout_marginEnd="10dp"
android:id="@+id/player_restart_root"
android:orientation="vertical"
android:visibility="gone">
<ImageView
android:id="@+id/player_restart"
android:layout_width="30dp"
android:layout_height="30dp"
android:layout_gravity="center"
android:src="@drawable/ic_baseline_replay_24"
app:tint="@android:color/white"
android:background="@drawable/video_tap_button_always_white"
android:clickable="true"
android:contentDescription="@string/restart"
android:focusable="true"
android:nextFocusRight="@id/player_go_forward"
android:nextFocusLeft="@id/player_go_back"
android:nextFocusDown="@id/player_pause_play"
android:nextFocusUp="@id/player_restart"
android:tag="@string/tv_no_focus_tag" />
<TextView
android:id="@+id/player_restart_text"
style="@style/ResultMarqueeButtonText"
android:text="@string/restart" />
</LinearLayout>
<LinearLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="80dp"
android:layout_marginEnd="10dp"
android:id="@+id/player_go_forward_root"
android:orientation="vertical"
android:visibility="gone">
<ImageView
android:id="@+id/player_go_forward"
android:layout_width="30dp"
android:layout_height="30dp"
android:layout_gravity="center"
android:src="@drawable/ic_baseline_skip_next_rounded_24"
app:tint="@android:color/white"
android:background="@drawable/video_tap_button_always_white"
android:clickable="true"
android:nextFocusRight="@id/player_go_forward"
android:nextFocusLeft="@id/player_restart"
android:nextFocusDown="@id/player_pause_play"
android:nextFocusUp="@id/player_go_forward"
android:contentDescription="@string/next_episode"
android:focusable="true"
android:tag="@string/tv_no_focus_tag" />
<TextView
android:id="@+id/player_go_forward_text"
style="@style/ResultMarqueeButtonText"
android:text="@string/next_episode" />
</LinearLayout>
<ImageView
android:id="@+id/player_go_back"
android:layout_width="70dp"
android:layout_height="70dp"
android:layout_gravity="center"
android:background="@drawable/video_tap_button_always_white"
android:clickable="true"
android:contentDescription="@string/go_back_img_des"
android:focusable="true" />
</FrameLayout> </FrameLayout>
@ -626,7 +707,7 @@
android:nextFocusLeft="@id/player_sources_btt" android:nextFocusLeft="@id/player_sources_btt"
android:nextFocusRight="@id/player_skip_op" android:nextFocusRight="@id/player_skip_op"
android:text="@string/tracks" android:text="@string/tracks"
app:icon="@drawable/ic_baseline_playlist_play_24" /> app:icon="@drawable/ic_baseline_equalizer_24" />
<com.google.android.material.button.MaterialButton <com.google.android.material.button.MaterialButton
android:id="@+id/player_skip_op" android:id="@+id/player_skip_op"
@ -641,10 +722,10 @@
android:id="@+id/player_skip_episode" android:id="@+id/player_skip_episode"
style="@style/VideoButton" style="@style/VideoButton"
android:nextFocusLeft="@id/player_skip_op" android:nextFocusLeft="@id/player_skip_op"
android:nextFocusRight="@id/player_lock" android:nextFocusRight="@id/player_lock"
android:text="@string/next_episode" android:text="@string/next_episode"
app:icon="@drawable/ic_baseline_skip_next_24" /> app:icon="@drawable/ic_baseline_skip_next_24" />
</LinearLayout> </LinearLayout>
</LinearLayout> </LinearLayout>
</HorizontalScrollView> </HorizontalScrollView>

View file

@ -231,7 +231,7 @@
<LinearLayout <LinearLayout
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_marginStart="80dp" android:layout_marginStart="32dp"
android:layout_marginTop="20dp" android:layout_marginTop="20dp"
android:layout_marginEnd="32dp" android:layout_marginEnd="32dp"
android:orientation="vertical"> android:orientation="vertical">
@ -240,6 +240,7 @@
android:id="@+id/player_video_title" android:id="@+id/player_video_title"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:textAlignment="viewEnd"
android:gravity="end" android:gravity="end"
android:textColor="@color/white" android:textColor="@color/white"
android:textSize="16sp" android:textSize="16sp"
@ -250,6 +251,7 @@
android:id="@+id/player_video_title_rez" android:id="@+id/player_video_title_rez"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:textAlignment="viewEnd"
android:gravity="end" android:gravity="end"
android:textColor="@color/white" android:textColor="@color/white"
android:textSize="16sp" android:textSize="16sp"
@ -285,28 +287,116 @@
android:id="@+id/player_go_back_holder" android:id="@+id/player_go_back_holder"
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_margin="5dp" android:layout_marginStart="17dp"
android:layout_marginTop="20dp"
android:layout_marginEnd="17dp"
app:layout_constraintStart_toStartOf="parent" app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"> app:layout_constraintTop_toTopOf="parent">
<ImageView <LinearLayout
android:layout_width="30dp" android:layout_width="60dp"
android:layout_height="30dp" android:layout_height="wrap_content"
android:layout_gravity="center" android:layout_marginEnd="10dp"
android:contentDescription="@string/go_back_img_des" android:id="@+id/player_go_back_root"
android:src="@drawable/ic_baseline_arrow_back_24" android:orientation="vertical">
app:tint="@android:color/white" />
<ImageView
android:id="@+id/player_go_back"
android:layout_width="30dp"
android:layout_height="30dp"
android:layout_gravity="center"
android:src="@drawable/ic_baseline_arrow_back_24"
app:tint="@android:color/white"
android:background="@drawable/video_tap_button_always_white"
android:clickable="true"
android:contentDescription="@string/go_back_img_des"
android:focusable="true"
android:nextFocusRight="@id/player_restart"
android:nextFocusLeft="@id/player_go_back"
android:nextFocusDown="@id/player_pause_play"
android:nextFocusUp="@id/player_go_back"
android:tag="@string/tv_no_focus_tag" />
<TextView
android:id="@+id/player_go_back_text"
android:layout_marginTop="5dp"
style="@style/ResultMarqueeButtonText"
android:text="@string/go_back_img_des"
android:visibility="invisible"
tools:visibility="visible"/>
</LinearLayout>
<LinearLayout
android:layout_width="60dp"
android:layout_height="wrap_content"
android:layout_marginStart="50dp"
android:layout_marginEnd="10dp"
android:id="@+id/player_restart_root"
android:orientation="vertical">
<ImageView
android:id="@+id/player_restart"
android:layout_width="30dp"
android:layout_height="30dp"
android:layout_gravity="center"
android:src="@drawable/ic_baseline_replay_24"
app:tint="@android:color/white"
android:background="@drawable/video_tap_button_always_white"
android:clickable="true"
android:contentDescription="@string/restart"
android:focusable="true"
android:nextFocusRight="@id/player_go_forward"
android:nextFocusLeft="@id/player_go_back"
android:nextFocusDown="@id/player_pause_play"
android:nextFocusUp="@id/player_restart"
android:tag="@string/tv_no_focus_tag" />
<TextView
android:id="@+id/player_restart_text"
android:layout_marginTop="5dp"
style="@style/ResultMarqueeButtonText"
android:text="@string/restart"
android:visibility="invisible"
tools:visibility="visible"/>
</LinearLayout>
<LinearLayout
android:layout_width="60dp"
android:layout_height="wrap_content"
android:layout_marginStart="100dp"
android:layout_marginEnd="10dp"
android:id="@+id/player_go_forward_root"
android:orientation="vertical">
<ImageView
android:id="@+id/player_go_forward"
android:layout_width="30dp"
android:layout_height="30dp"
android:layout_gravity="center"
android:src="@drawable/ic_baseline_skip_next_rounded_24"
app:tint="@android:color/white"
android:background="@drawable/video_tap_button_always_white"
android:clickable="true"
android:nextFocusRight="@id/player_go_forward"
android:nextFocusLeft="@id/player_restart"
android:nextFocusDown="@id/player_pause_play"
android:nextFocusUp="@id/player_go_forward"
android:contentDescription="@string/next_episode"
android:focusable="true"
android:tag="@string/tv_no_focus_tag" />
<TextView
android:id="@+id/player_go_forward_text"
android:layout_marginTop="5dp"
style="@style/ResultMarqueeButtonText"
android:text="@string/next_episode"
android:visibility="invisible"
tools:visibility="visible"/>
</LinearLayout>
<ImageView
android:id="@+id/player_go_back"
android:layout_width="70dp"
android:layout_height="70dp"
android:layout_gravity="center"
android:background="@drawable/video_tap_button_always_white"
android:clickable="true"
android:contentDescription="@string/go_back_img_des"
android:focusable="true"
android:tag="@string/tv_no_focus_tag" />
</FrameLayout> </FrameLayout>
</FrameLayout> </FrameLayout>
@ -509,6 +599,7 @@
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layoutDirection="ltr" android:layoutDirection="ltr"
android:orientation="horizontal"> android:orientation="horizontal">
<ImageView <ImageView
android:id="@+id/player_pause_play" android:id="@+id/player_pause_play"
@ -520,14 +611,14 @@
android:focusable="true" android:focusable="true"
android:focusableInTouchMode="true" android:focusableInTouchMode="true"
android:nextFocusUp="@id/skip_chapter_button" android:nextFocusUp="@id/skip_chapter_button"
android:nextFocusDown="@id/player_skip_op" android:nextFocusDown="@id/player_lock_holder"
android:src="@drawable/netflix_pause" android:src="@drawable/netflix_pause"
android:tag="@string/tv_no_focus_tag" android:tag="@string/tv_no_focus_tag"
app:tint="@color/player_button_tv"
tools:ignore="ContentDescription"
app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toStartOf="parent" /> app:layout_constraintStart_toStartOf="parent"
app:tint="@color/player_button_tv"
tools:ignore="ContentDescription" />
<TextView <TextView
android:id="@id/exo_position" android:id="@id/exo_position"
@ -618,10 +709,10 @@
android:textColor="@android:color/white" android:textColor="@android:color/white"
android:textSize="14sp" android:textSize="14sp"
android:textStyle="normal" android:textStyle="normal"
android:visibility="gone"
app:layout_constraintBaseline_toBaselineOf="@id/exo_position" app:layout_constraintBaseline_toBaselineOf="@id/exo_position"
app:layout_constraintEnd_toEndOf="parent" app:layout_constraintEnd_toEndOf="parent"
tools:text="-23:20" tools:text="-23:20" />
android:visibility="gone"/>
</androidx.constraintlayout.widget.ConstraintLayout> </androidx.constraintlayout.widget.ConstraintLayout>
@ -672,12 +763,13 @@
android:id="@+id/player_skip_episode" android:id="@+id/player_skip_episode"
style="@style/VideoButtonTV" style="@style/VideoButtonTV"
android:nextFocusLeft="@id/player_skip_op" android:nextFocusLeft="@id/player_skip_op"
android:nextFocusRight="@id/player_resize_btt" android:nextFocusRight="@id/player_resize_btt"
android:nextFocusUp="@id/player_pause_play" android:nextFocusUp="@id/player_pause_play"
android:nextFocusDown="@id/player_resize_btt" android:nextFocusDown="@id/player_resize_btt"
android:text="@string/next_episode" android:text="@string/next_episode"
app:icon="@drawable/ic_baseline_skip_next_24" /> app:icon="@drawable/ic_baseline_skip_next_24"
android:visibility="gone"
tools:visibility="visible"/>
<com.google.android.material.button.MaterialButton <com.google.android.material.button.MaterialButton
android:id="@+id/player_resize_btt" android:id="@+id/player_resize_btt"
@ -732,7 +824,7 @@
android:nextFocusRight="@id/player_skip_op" android:nextFocusRight="@id/player_skip_op"
android:nextFocusUp="@id/player_pause_play" android:nextFocusUp="@id/player_pause_play"
android:text="@string/tracks" android:text="@string/tracks"
app:icon="@drawable/ic_baseline_playlist_play_24" /> app:icon="@drawable/ic_baseline_equalizer_24" />
</LinearLayout> </LinearLayout>
</HorizontalScrollView> </HorizontalScrollView>
</LinearLayout> </LinearLayout>

View file

@ -44,7 +44,7 @@
android:nextFocusRight="@id/download_button" android:nextFocusRight="@id/download_button"
android:scaleType="centerCrop" android:scaleType="centerCrop"
tools:src="@drawable/example_poster" tools:src="@drawable/example_poster"
tools:visibility="invisible"/> tools:visibility="invisible" />
<ImageView <ImageView
android:id="@+id/episode_play_icon" android:id="@+id/episode_play_icon"
@ -53,7 +53,7 @@
android:layout_gravity="center" android:layout_gravity="center"
android:contentDescription="@string/play_episode" android:contentDescription="@string/play_episode"
android:src="@drawable/play_button" android:src="@drawable/play_button"
tools:visibility="invisible"/> tools:visibility="invisible" />
<ImageView <ImageView
android:id="@+id/episode_upcoming_icon" android:id="@+id/episode_upcoming_icon"
@ -106,12 +106,29 @@
tools:text="1. Jobless" /> tools:text="1. Jobless" />
</LinearLayout> </LinearLayout>
<TextView <LinearLayout
android:id="@+id/episode_rating"
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:textColor="?attr/grayTextColor" android:layout_gravity="start"
tools:text="Rated: 8.8" /> android:orientation="horizontal">
<TextView
android:id="@+id/episode_rating"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginEnd="10dp"
android:textColor="?attr/grayTextColor"
tools:text="Rated: 8.8" />
<TextView
android:id="@+id/episode_runtime"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginEnd="10dp"
android:textColor="?attr/grayTextColor"
tools:text="80min" />
</LinearLayout>
<TextView <TextView
android:id="@+id/episode_date" android:id="@+id/episode_date"
@ -119,6 +136,7 @@
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:textColor="?attr/grayTextColor" android:textColor="?attr/grayTextColor"
tools:text="15 Apr 2024" /> tools:text="15 Apr 2024" />
</LinearLayout> </LinearLayout>
<com.lagradost.cloudstream3.ui.download.button.PieFetchButton <com.lagradost.cloudstream3.ui.download.button.PieFetchButton

View file

@ -30,28 +30,27 @@
<ImageView <ImageView
android:id="@+id/subtitle_offset_subtract_more" android:id="@+id/subtitle_offset_subtract_more"
android:layout_width="wrap_content" android:layout_width="24dp"
android:layout_height="match_parent" android:layout_height="24dp"
android:layout_gravity="center" android:layout_gravity="center"
android:layout_weight="1" android:layout_marginStart="10dp"
android:layout_marginEnd="10dp"
android:background="?android:attr/selectableItemBackgroundBorderless" android:background="?android:attr/selectableItemBackgroundBorderless"
android:focusable="true" android:focusable="true"
android:nextFocusRight="@id/subtitle_offset_subtract" android:nextFocusRight="@id/subtitle_offset_subtract"
android:padding="10dp"
android:src="@drawable/ic_baseline_keyboard_arrow_left_24" android:src="@drawable/ic_baseline_keyboard_arrow_left_24"
app:tint="?attr/white" app:tint="?attr/white"
tools:ignore="ContentDescription" /> tools:ignore="ContentDescription" />
<ImageView <ImageView
android:id="@+id/subtitle_offset_subtract" android:id="@+id/subtitle_offset_subtract"
android:layout_width="wrap_content" android:layout_width="24dp"
android:layout_height="match_parent" android:layout_height="24dp"
android:layout_gravity="center" android:layout_gravity="center"
android:layout_weight="1" android:layout_marginEnd="10dp"
android:background="?android:attr/selectableItemBackgroundBorderless" android:background="?android:attr/selectableItemBackgroundBorderless"
android:focusable="true" android:focusable="true"
android:nextFocusLeft="@id/subtitle_offset_subtract_more" android:nextFocusLeft="@id/subtitle_offset_subtract_more"
android:padding="10dp"
android:src="@drawable/baseline_remove_24" android:src="@drawable/baseline_remove_24"
app:tint="?attr/white" app:tint="?attr/white"
tools:ignore="ContentDescription" /> tools:ignore="ContentDescription" />
@ -67,29 +66,29 @@
<ImageView <ImageView
android:id="@+id/subtitle_offset_add" android:id="@+id/subtitle_offset_add"
android:layout_width="wrap_content" android:layout_width="24dp"
android:layout_height="match_parent" android:layout_height="24dp"
android:layout_gravity="center" android:layout_gravity="center"
android:layout_weight="1" android:layout_marginStart="10dp"
android:background="?android:attr/selectableItemBackgroundBorderless" android:background="?android:attr/selectableItemBackgroundBorderless"
android:focusable="true" android:focusable="true"
android:nextFocusRight="@id/subtitle_offset_add_more" android:nextFocusRight="@id/subtitle_offset_add_more"
android:padding="10dp"
android:src="@drawable/ic_baseline_add_24" android:src="@drawable/ic_baseline_add_24"
app:tint="?attr/white" app:tint="?attr/white"
tools:ignore="ContentDescription" /> tools:ignore="ContentDescription" />
<ImageView <ImageView
android:id="@+id/subtitle_offset_add_more" android:id="@+id/subtitle_offset_add_more"
android:layout_width="wrap_content" android:layout_width="24dp"
android:layout_height="match_parent" android:layout_height="24dp"
android:layout_gravity="center" android:layout_gravity="center"
android:layout_weight="1" android:layout_marginStart="10dp"
android:layout_marginEnd="10dp"
android:background="?android:attr/selectableItemBackgroundBorderless" android:background="?android:attr/selectableItemBackgroundBorderless"
android:focusable="true" android:focusable="true"
android:nextFocusLeft="@id/subtitle_offset_add" android:nextFocusLeft="@id/subtitle_offset_add"
android:nextFocusDown="@id/apply_btt" android:nextFocusDown="@id/apply_btt"
android:padding="10dp"
android:src="@drawable/ic_baseline_keyboard_arrow_right_24" android:src="@drawable/ic_baseline_keyboard_arrow_right_24"
app:tint="?attr/white" app:tint="?attr/white"
tools:ignore="ContentDescription" /> tools:ignore="ContentDescription" />

View file

@ -137,33 +137,110 @@
<FrameLayout <FrameLayout
android:id="@+id/player_go_back_holder" android:id="@+id/player_go_back_holder"
android:layout_width="0dp" android:layout_width="wrap_content"
android:layout_height="0dp" android:layout_height="wrap_content"
android:layout_margin="5dp" android:layout_margin="20dp"
android:clickable="false"
android:focusable="false"
android:focusableInTouchMode="false"
android:visibility="gone"
app:layout_constraintStart_toStartOf="parent" app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"> app:layout_constraintTop_toTopOf="parent">
<ImageView <LinearLayout
android:layout_width="30dp" android:layout_width="wrap_content"
android:layout_height="30dp" android:layout_height="wrap_content"
android:layout_gravity="center" android:layout_marginEnd="10dp"
android:contentDescription="@string/go_back_img_des" android:id="@+id/player_go_back_root"
android:src="@drawable/ic_baseline_arrow_back_24" android:orientation="vertical">
app:tint="@android:color/white" />
<ImageView
android:id="@+id/player_go_back"
android:layout_width="30dp"
android:layout_height="30dp"
android:layout_gravity="center"
android:src="@drawable/ic_baseline_arrow_back_24"
app:tint="@android:color/white"
android:background="@drawable/video_tap_button_always_white"
android:clickable="true"
android:contentDescription="@string/go_back_img_des"
android:focusable="true"
android:nextFocusRight="@id/player_restart"
android:nextFocusLeft="@id/player_go_back"
android:nextFocusDown="@id/player_pause_play"
android:nextFocusUp="@id/player_go_back"
android:tag="@string/tv_no_focus_tag" />
<TextView
android:id="@+id/player_go_back_text"
style="@style/ResultMarqueeButtonText"
android:text="@string/go_back_img_des"
android:visibility="gone"/>
</LinearLayout>
<LinearLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="40dp"
android:layout_marginEnd="10dp"
android:id="@+id/player_restart_root"
android:orientation="vertical">
<ImageView
android:id="@+id/player_restart"
android:layout_width="30dp"
android:layout_height="30dp"
android:layout_gravity="center"
android:src="@drawable/ic_baseline_replay_24"
app:tint="@android:color/white"
android:background="@drawable/video_tap_button_always_white"
android:clickable="true"
android:contentDescription="@string/restart"
android:focusable="true"
android:nextFocusRight="@id/player_go_forward"
android:nextFocusLeft="@id/player_go_back"
android:nextFocusDown="@id/player_pause_play"
android:nextFocusUp="@id/player_restart"
android:tag="@string/tv_no_focus_tag" />
<TextView
android:id="@+id/player_restart_text"
style="@style/ResultMarqueeButtonText"
android:text="@string/restart"
android:visibility="gone"/>
</LinearLayout>
<LinearLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="80dp"
android:layout_marginEnd="10dp"
android:id="@+id/player_go_forward_root"
android:orientation="vertical">
<ImageView
android:id="@+id/player_go_forward"
android:layout_width="30dp"
android:layout_height="30dp"
android:layout_gravity="center"
android:src="@drawable/ic_baseline_skip_next_rounded_24"
app:tint="@android:color/white"
android:background="@drawable/video_tap_button_always_white"
android:clickable="true"
android:nextFocusRight="@id/player_go_forward"
android:nextFocusLeft="@id/player_restart"
android:nextFocusDown="@id/player_pause_play"
android:nextFocusUp="@id/player_go_forward"
android:contentDescription="@string/next_episode"
android:focusable="true"
android:tag="@string/tv_no_focus_tag" />
<TextView
android:id="@+id/player_go_forward_text"
style="@style/ResultMarqueeButtonText"
android:text="@string/next_episode"
android:visibility="gone"/>
</LinearLayout>
<ImageView
android:id="@+id/player_go_back"
android:layout_width="70dp"
android:layout_height="70dp"
android:layout_gravity="center"
android:background="@drawable/video_tap_button_always_white"
android:clickable="true"
android:contentDescription="@string/go_back_img_des"
android:focusable="true" />
</FrameLayout> </FrameLayout>
</FrameLayout> </FrameLayout>

View file

@ -106,4 +106,5 @@
<string name="subs_import_text" formatted="true">Voer lettertipes in deur dit in %s te plaas</string> <string name="subs_import_text" formatted="true">Voer lettertipes in deur dit in %s te plaas</string>
<string name="cast_format" formatted="true">Rolverdeling: %s</string> <string name="cast_format" formatted="true">Rolverdeling: %s</string>
<string name="subscribe_tooltip">Nuwe episode notifikasie</string> <string name="subscribe_tooltip">Nuwe episode notifikasie</string>
<string name="hide_player_control_names_key" translatable="false">hide_player_control_names_key</string>
</resources> </resources>

View file

@ -640,4 +640,5 @@
<string name="dismiss">تجاهل</string> <string name="dismiss">تجاهل</string>
<string name="open_downloaded_repo">متاح الريپوزيتوري</string> <string name="open_downloaded_repo">متاح الريپوزيتوري</string>
<string name="device_pin_url_message">فتاح <b>%s</b> ع تلفونك أو كمپيوترك، وحط الكود اللي فوق</string> <string name="device_pin_url_message">فتاح <b>%s</b> ع تلفونك أو كمپيوترك، وحط الكود اللي فوق</string>
<string name="hide_player_control_names_key" translatable="false">hide_player_control_names_key</string>
</resources> </resources>

View file

@ -108,4 +108,5 @@
<string name="action_open_watching">ተጨማሪ መረጃ</string> <string name="action_open_watching">ተጨማሪ መረጃ</string>
<string name="search_provider_text_types">ዓይነቶችን በመጠቀም ይፈልጉ</string> <string name="search_provider_text_types">ዓይነቶችን በመጠቀም ይፈልጉ</string>
<string name="subs_import_text" formatted="true">ቅርጸ-ቁምፊዎችን በ%s ውስጥ በማስቀመጥ ያጫኑ</string> <string name="subs_import_text" formatted="true">ቅርጸ-ቁምፊዎችን በ%s ውስጥ በማስቀመጥ ያጫኑ</string>
<string name="hide_player_control_names_key" translatable="false">hide_player_control_names_key</string>
</resources> </resources>

View file

@ -666,4 +666,5 @@
<string name="device_pin_url_message">قم بزيارة <b>%s</b> على هاتفك الذكي أو جهاز الكمبيوتر وأدخل الرمز أعلاه</string> <string name="device_pin_url_message">قم بزيارة <b>%s</b> على هاتفك الذكي أو جهاز الكمبيوتر وأدخل الرمز أعلاه</string>
<string name="device_pin_error_message">لا يمكن الحصول على رمز PIN للجهاز، حاول المصادقة المحلية</string> <string name="device_pin_error_message">لا يمكن الحصول على رمز PIN للجهاز، حاول المصادقة المحلية</string>
<string name="device_pin_counter_text">تنتهي صلاحية الرمز خلال %1$dm %2$ds</string> <string name="device_pin_counter_text">تنتهي صلاحية الرمز خلال %1$dm %2$ds</string>
<string name="hide_player_control_names_key" translatable="false">hide_player_control_names_key</string>
</resources> </resources>

View file

@ -352,4 +352,5 @@
<string name="documentaries_singular">وثائقي</string> <string name="documentaries_singular">وثائقي</string>
<string name="site">موقع</string> <string name="site">موقع</string>
<string name="limit_title">عنوان مشغل الفيديو بحد أقصى لعدد الأحرف</string> <string name="limit_title">عنوان مشغل الفيديو بحد أقصى لعدد الأحرف</string>
<string name="hide_player_control_names_key" translatable="false">hide_player_control_names_key</string>
</resources> </resources>

View file

@ -621,4 +621,5 @@
<string name="pick_subtitle">ছাবটাইটেল বাছনি কৰক</string> <string name="pick_subtitle">ছাবটাইটেল বাছনি কৰক</string>
<string name="play_episode">পৰ্ব খেলাওক</string> <string name="play_episode">পৰ্ব খেলাওক</string>
<string name="sort_apply">প্ৰয়োগ কৰক</string> <string name="sort_apply">প্ৰয়োগ কৰক</string>
<string name="hide_player_control_names_key" translatable="false">hide_player_control_names_key</string>
</resources> </resources>

View file

@ -601,4 +601,5 @@
<string name="recommendations_tooltip">Покажи предложения</string> <string name="recommendations_tooltip">Покажи предложения</string>
<string name="speed_setting_summary">Добавя опция за промяна на скоростта в плеъра</string> <string name="speed_setting_summary">Добавя опция за промяна на скоростта в плеъра</string>
<string name="test_extensions_summary">Този тест е направен за програмисти и не проверява работата на никакви добавки.</string> <string name="test_extensions_summary">Този тест е направен за програмисти и не проверява работата на никакви добавки.</string>
<string name="hide_player_control_names_key" translatable="false">hide_player_control_names_key</string>
</resources> </resources>

View file

@ -359,4 +359,5 @@
<string name="account">অ্যাকাউন্ট</string> <string name="account">অ্যাকাউন্ট</string>
<string name="logout">প্রস্থান</string> <string name="logout">প্রস্থান</string>
<string name="episode_format" formatted="true">%1$d%2$s</string> <string name="episode_format" formatted="true">%1$d%2$s</string>
<string name="hide_player_control_names_key" translatable="false">hide_player_control_names_key</string>
</resources> </resources>

View file

@ -656,4 +656,5 @@
<string name="device_pin_error_message">Não é possível obter o código PIN do dispositivo, tente a autenticação local</string> <string name="device_pin_error_message">Não é possível obter o código PIN do dispositivo, tente a autenticação local</string>
<string name="device_pin_expired_message">O código PIN expirou!</string> <string name="device_pin_expired_message">O código PIN expirou!</string>
<string name="device_pin_counter_text">O código expira em %1$dm %2$ds</string> <string name="device_pin_counter_text">O código expira em %1$dm %2$ds</string>
<string name="hide_player_control_names_key" translatable="false">hide_player_control_names_key</string>
</resources> </resources>

View file

@ -658,4 +658,5 @@
<string name="pref_category_accounts">Účty</string> <string name="pref_category_accounts">Účty</string>
<string name="auth_locally">Lokální ověření</string> <string name="auth_locally">Lokální ověření</string>
<string name="device_pin_expired_message">PIN kód vypršel!</string> <string name="device_pin_expired_message">PIN kód vypršel!</string>
<string name="hide_player_control_names_key" translatable="false">hide_player_control_names_key</string>
</resources> </resources>

View file

@ -615,4 +615,5 @@
<string name="reset_btn">Zurücksetzen</string> <string name="reset_btn">Zurücksetzen</string>
<string name="app_unrestricted_toast">Akkuverbrauch der App ist bereits auf unbeschränkt eingestellt</string> <string name="app_unrestricted_toast">Akkuverbrauch der App ist bereits auf unbeschränkt eingestellt</string>
<string name="app_info_intent_error">CloudStreams App-Info kann nicht geöffnet werden.</string> <string name="app_info_intent_error">CloudStreams App-Info kann nicht geöffnet werden.</string>
<string name="hide_player_control_names_key" translatable="false">hide_player_control_names_key</string>
</resources> </resources>

View file

@ -625,4 +625,5 @@
<string name="biometric_warning">Τα δεδομένα σας στο CloudStream έχουν κάνει back up. Αν και η πιθανότητα είναι πολύ χαμηλή, όλες οι συσκευές συμπεριφέρονται διαφορετικά. Στη σπάνια περίπτωση, που απαγορευτεί η πρόσβασή σας από την εφαρμογή, διαγράψτε τα δεδομένα εφαρμογής και επαναφέρετέ τα από ένα ήδη υπάρχον backup. Συγνώμη για οποιαδήποτε ταλαιπωρία.</string> <string name="biometric_warning">Τα δεδομένα σας στο CloudStream έχουν κάνει back up. Αν και η πιθανότητα είναι πολύ χαμηλή, όλες οι συσκευές συμπεριφέρονται διαφορετικά. Στη σπάνια περίπτωση, που απαγορευτεί η πρόσβασή σας από την εφαρμογή, διαγράψτε τα δεδομένα εφαρμογής και επαναφέρετέ τα από ένα ήδη υπάρχον backup. Συγνώμη για οποιαδήποτε ταλαιπωρία.</string>
<string name="pref_category_accounts">Λογαριασμοί</string> <string name="pref_category_accounts">Λογαριασμοί</string>
<string name="pref_category_security">Ασφάλεια</string> <string name="pref_category_security">Ασφάλεια</string>
<string name="hide_player_control_names_key" translatable="false">hide_player_control_names_key</string>
</resources> </resources>

View file

@ -127,4 +127,5 @@
<string name="downloaded">Elŝutite</string> <string name="downloaded">Elŝutite</string>
<string name="downloading">Elŝutante</string> <string name="downloading">Elŝutante</string>
<string name="download_failed">Elŝuto Malsukcesite</string> <string name="download_failed">Elŝuto Malsukcesite</string>
<string name="hide_player_control_names_key" translatable="false">hide_player_control_names_key</string>
</resources> </resources>

View file

@ -634,4 +634,5 @@
<string name="device_pin_expired_message">¡El código PIN ya ha caducado!</string> <string name="device_pin_expired_message">¡El código PIN ya ha caducado!</string>
<string name="device_pin_counter_text">El código caduca en %1$d mín y %2$d s</string> <string name="device_pin_counter_text">El código caduca en %1$d mín y %2$d s</string>
<string name="device_pin_error_message">No puedo obtener el código PIN del dispositivo; intente con la autenticación local</string> <string name="device_pin_error_message">No puedo obtener el código PIN del dispositivo; intente con la autenticación local</string>
<string name="hide_player_control_names_key" translatable="false">hide_player_control_names_key</string>
</resources> </resources>

View file

@ -191,4 +191,5 @@
<string name="action_default">پیش‌فرض</string> <string name="action_default">پیش‌فرض</string>
<string name="cartoons_singular">کارتون</string> <string name="cartoons_singular">کارتون</string>
<string name="torrent_singular">تورنت</string> <string name="torrent_singular">تورنت</string>
<string name="hide_player_control_names_key" translatable="false">hide_player_control_names_key</string>
</resources> </resources>

View file

@ -1,2 +1,4 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<resources/> <resources>
<string name="hide_player_control_names_key" translatable="false">hide_player_control_names_key</string>
</resources>

View file

@ -620,4 +620,5 @@
<string name="biometric_setting">Verrouillage biométrique</string> <string name="biometric_setting">Verrouillage biométrique</string>
<string name="player_settings_select_cast_device">Sélectionnez un appareil de diffusion</string> <string name="player_settings_select_cast_device">Sélectionnez un appareil de diffusion</string>
<string name="next_season_episode_format" formatted="true">Saison %1$d Episode %2$d sera publié dans</string> <string name="next_season_episode_format" formatted="true">Saison %1$d Episode %2$d sera publié dans</string>
<string name="hide_player_control_names_key" translatable="false">hide_player_control_names_key</string>
</resources> </resources>

View file

@ -164,4 +164,5 @@
<string name="automatic_plugin_download_mode_title">Selecciona o modo para filtrar a descarga dos complementos</string> <string name="automatic_plugin_download_mode_title">Selecciona o modo para filtrar a descarga dos complementos</string>
<string name="automatic_plugin_download_summary">Instala automáticamente todos os complementos aínda non instalados dos repositorios engadidos.</string> <string name="automatic_plugin_download_summary">Instala automáticamente todos os complementos aínda non instalados dos repositorios engadidos.</string>
<string name="updates_settings">Mostrar actualizacións da aplicación</string> <string name="updates_settings">Mostrar actualizacións da aplicación</string>
<string name="hide_player_control_names_key" translatable="false">hide_player_control_names_key</string>
</resources> </resources>

View file

@ -209,4 +209,5 @@
<string name="subs_outline_color">रूपरेखा रंग</string> <string name="subs_outline_color">रूपरेखा रंग</string>
<string name="subs_subtitle_elevation">उपशीर्षक ऊंचाई</string> <string name="subs_subtitle_elevation">उपशीर्षक ऊंचाई</string>
<string name="subs_font">अक्षर शैली</string> <string name="subs_font">अक्षर शैली</string>
<string name="hide_player_control_names_key" translatable="false">hide_player_control_names_key</string>
</resources> </resources>

View file

@ -651,4 +651,5 @@
<string name="cs3wiki">CloudStream Wiki</string> <string name="cs3wiki">CloudStream Wiki</string>
<string name="pref_category_accounts">Računi</string> <string name="pref_category_accounts">Računi</string>
<string name="pref_category_security">Sigurnost</string> <string name="pref_category_security">Sigurnost</string>
<string name="hide_player_control_names_key" translatable="false">hide_player_control_names_key</string>
</resources> </resources>

View file

@ -592,4 +592,5 @@
<string name="pin_error_length">A PIN 4 karakter hosszú kell legyen</string> <string name="pin_error_length">A PIN 4 karakter hosszú kell legyen</string>
<string name="auto_rotate_video">Auto elforgatás</string> <string name="auto_rotate_video">Auto elforgatás</string>
<string name="auto_rotate_video_desc">Az automatikus videó orientáció alapján való képernyő elforgatás bekapcsolása</string> <string name="auto_rotate_video_desc">Az automatikus videó orientáció alapján való képernyő elforgatás bekapcsolása</string>
<string name="hide_player_control_names_key" translatable="false">hide_player_control_names_key</string>
</resources> </resources>

View file

@ -647,4 +647,5 @@
<string name="cs3wiki">CloudStream Wiki</string> <string name="cs3wiki">CloudStream Wiki</string>
<string name="pref_category_security">Keamanan</string> <string name="pref_category_security">Keamanan</string>
<string name="pref_category_accounts">Akun</string> <string name="pref_category_accounts">Akun</string>
<string name="hide_player_control_names_key" translatable="false">hide_player_control_names_key</string>
</resources> </resources>

View file

@ -654,4 +654,5 @@
<string name="device_pin_error_message">Impossibile ottenere il codice PIN del dispositivo, prova l\'autenticazione locale</string> <string name="device_pin_error_message">Impossibile ottenere il codice PIN del dispositivo, prova l\'autenticazione locale</string>
<string name="device_pin_expired_message">Il codice PIN è scaduto!</string> <string name="device_pin_expired_message">Il codice PIN è scaduto!</string>
<string name="device_pin_counter_text">Il codice scadrà tra %1$dm %2$ds</string> <string name="device_pin_counter_text">Il codice scadrà tra %1$dm %2$ds</string>
<string name="hide_player_control_names_key" translatable="false">hide_player_control_names_key</string>
</resources> </resources>

View file

@ -550,4 +550,5 @@
\nיגרמו לעדיפות הסרטון להיות 10. \nיגרמו לעדיפות הסרטון להיות 10.
\n \n
\nשימו לב: אם הסכום הוא 10 או יותר, הנגן ידלג על טעינת הסרטון כאשר הלינק נטען!</string> \nשימו לב: אם הסכום הוא 10 או יותר, הנגן ידלג על טעינת הסרטון כאשר הלינק נטען!</string>
<string name="hide_player_control_names_key" translatable="false">hide_player_control_names_key</string>
</resources> </resources>

View file

@ -242,4 +242,5 @@
<string name="autoplay_next_settings_des">現在のエピソードが終了したら次のエピソードを開始する</string> <string name="autoplay_next_settings_des">現在のエピソードが終了したら次のエピソードを開始する</string>
<string name="subs_hold_to_reset_to_default">長押しするとデフォルトにリセットされます</string> <string name="subs_hold_to_reset_to_default">長押しするとデフォルトにリセットされます</string>
<string name="popup_resume_download">ダウンロードを再開</string> <string name="popup_resume_download">ダウンロードを再開</string>
<string name="hide_player_control_names_key" translatable="false">hide_player_control_names_key</string>
</resources> </resources>

View file

@ -130,4 +130,5 @@
<string name="swipe_to_change_settings_des">Brightness ಅಥವಾ volume ಬದಲಾಯಿಸಲು ಎಡ ಅಥವಾ ಬಲಭಾಗದಲ್ಲಿ ಮೇಲಕ್ಕೆ ಅಥವಾ ಕೆಳಕ್ಕೆ ಸ್ಲೈಡ್ ಮಾಡಿ</string> <string name="swipe_to_change_settings_des">Brightness ಅಥವಾ volume ಬದಲಾಯಿಸಲು ಎಡ ಅಥವಾ ಬಲಭಾಗದಲ್ಲಿ ಮೇಲಕ್ಕೆ ಅಥವಾ ಕೆಳಕ್ಕೆ ಸ್ಲೈಡ್ ಮಾಡಿ</string>
<string name="autoplay_next_settings_des">ಈಗಿನ ಎಪಿಸೋಡ್ ಮುಗಿದಾಗ ಮುಂದಿನ ಎಪಿಸೋಡ್ ಅನ್ನು ಪ್ರಾರಂಭಿಸಿ</string> <string name="autoplay_next_settings_des">ಈಗಿನ ಎಪಿಸೋಡ್ ಮುಗಿದಾಗ ಮುಂದಿನ ಎಪಿಸೋಡ್ ಅನ್ನು ಪ್ರಾರಂಭಿಸಿ</string>
<string name="swipe_to_change_settings">ಸೆಟ್ಟಿಂಗ್‌ಗಳನ್ನು ಬದಲಾಯಿಸಲು ಸ್ವೈಪ್ ಮಾಡಿ</string> <string name="swipe_to_change_settings">ಸೆಟ್ಟಿಂಗ್‌ಗಳನ್ನು ಬದಲಾಯಿಸಲು ಸ್ವೈಪ್ ಮಾಡಿ</string>
<string name="hide_player_control_names_key" translatable="false">hide_player_control_names_key</string>
</resources> </resources>

View file

@ -634,4 +634,5 @@
<string name="enter_pin_with_name" formatted="true">%s의 PIN 입력</string> <string name="enter_pin_with_name" formatted="true">%s의 PIN 입력</string>
<string name="action_remove_from_favorites">즐겨찾기에서 제거</string> <string name="action_remove_from_favorites">즐겨찾기에서 제거</string>
<string name="episode_action_cast_mirror">캐스트미러</string> <string name="episode_action_cast_mirror">캐스트미러</string>
<string name="hide_player_control_names_key" translatable="false">hide_player_control_names_key</string>
</resources> </resources>

View file

@ -260,4 +260,5 @@
<string name="confirm_exit_dialog">Ar tikrai norite išeiti\?</string> <string name="confirm_exit_dialog">Ar tikrai norite išeiti\?</string>
<string name="action_remove_from_watched">Pašalinti iš žiūrimų</string> <string name="action_remove_from_watched">Pašalinti iš žiūrimų</string>
<string name="audio_tracks">Garso takelis</string> <string name="audio_tracks">Garso takelis</string>
<string name="hide_player_control_names_key" translatable="false">hide_player_control_names_key</string>
</resources> </resources>

View file

@ -527,4 +527,5 @@
<string name="subscription_in_progress_notification">Abonēto šovu atjaunināšana</string> <string name="subscription_in_progress_notification">Abonēto šovu atjaunināšana</string>
<string name="subscription_list_name">Abonēts</string> <string name="subscription_list_name">Abonēts</string>
<string name="subscription_new">Abonēts %s</string> <string name="subscription_new">Abonēts %s</string>
<string name="hide_player_control_names_key" translatable="false">hide_player_control_names_key</string>
</resources> </resources>

View file

@ -624,4 +624,5 @@
<string name="clipboard_permission_error">Грешка при пристапот до таблата со исечоци, обидете се повторно.</string> <string name="clipboard_permission_error">Грешка при пристапот до таблата со исечоци, обидете се повторно.</string>
<string name="clipboard_unknown_error">Грешка при копирање, копирајте го logcat и контактирајте со поддршката за апликацијата.</string> <string name="clipboard_unknown_error">Грешка при копирање, копирајте го logcat и контактирајте со поддршката за апликацијата.</string>
<string name="audio_book_singular">Аудио книга</string> <string name="audio_book_singular">Аудио книга</string>
<string name="hide_player_control_names_key" translatable="false">hide_player_control_names_key</string>
</resources> </resources>

View file

@ -280,4 +280,5 @@
<string name="subs_edge_type">എഡ്ജ് തരം</string> <string name="subs_edge_type">എഡ്ജ് തരം</string>
<string name="subs_outline_color">ഔട്ട്ലൈൻ നിറം</string> <string name="subs_outline_color">ഔട്ട്ലൈൻ നിറം</string>
<string name="subs_background_color">പശ്ചാത്തല നിറം</string> <string name="subs_background_color">പശ്ചാത്തല നിറം</string>
<string name="hide_player_control_names_key" translatable="false">hide_player_control_names_key</string>
</resources> </resources>

View file

@ -57,4 +57,5 @@
<string name="sort_close">Tutup</string> <string name="sort_close">Tutup</string>
<string name="app_dub_sub_episode_text_format" formatted="true">Ep</string> <string name="app_dub_sub_episode_text_format" formatted="true">Ep</string>
<string name="next_season_episode_format" formatted="true">cuba</string> <string name="next_season_episode_format" formatted="true">cuba</string>
<string name="hide_player_control_names_key" translatable="false">hide_player_control_names_key</string>
</resources> </resources>

View file

@ -123,4 +123,5 @@
<string name="error_bookmarks_text">Bookmarks</string> <string name="error_bookmarks_text">Bookmarks</string>
<string name="action_remove_from_bookmarks">Neħħi</string> <string name="action_remove_from_bookmarks">Neħħi</string>
<string name="download_failed">Falla t-tniżżil</string> <string name="download_failed">Falla t-tniżżil</string>
<string name="hide_player_control_names_key" translatable="false">hide_player_control_names_key</string>
</resources> </resources>

View file

@ -550,4 +550,5 @@
<string name="already_voted">သင်နဂိုတည်းကသတ်မှတ်ပြီး</string> <string name="already_voted">သင်နဂိုတည်းကသတ်မှတ်ပြီး</string>
<string name="select_library">လိုက်ဘရီရွေးချယ်ရန်</string> <string name="select_library">လိုက်ဘရီရွေးချယ်ရန်</string>
<string name="open_with">ဖြင့်ဖွင့်မည်</string> <string name="open_with">ဖြင့်ဖွင့်မည်</string>
<string name="hide_player_control_names_key" translatable="false">hide_player_control_names_key</string>
</resources> </resources>

View file

@ -128,4 +128,5 @@
<string name="player_subtitles_settings_des">प्लेयरको उपशीर्षकको सेटिङ</string> <string name="player_subtitles_settings_des">प्लेयरको उपशीर्षकको सेटिङ</string>
<string name="repo_copy_label">रिपोजिटरी को नाम र यूआरएल</string> <string name="repo_copy_label">रिपोजिटरी को नाम र यूआरएल</string>
<string name="toast_copied">कपी गरियो!</string> <string name="toast_copied">कपी गरियो!</string>
<string name="hide_player_control_names_key" translatable="false">hide_player_control_names_key</string>
</resources> </resources>

View file

@ -608,4 +608,5 @@
<string name="links_reloaded_toast">Link opnieuw geladen</string> <string name="links_reloaded_toast">Link opnieuw geladen</string>
<string name="auto_rotate_video">Autoroteer</string> <string name="auto_rotate_video">Autoroteer</string>
<string name="rotate_video">Roteer</string> <string name="rotate_video">Roteer</string>
<string name="hide_player_control_names_key" translatable="false">hide_player_control_names_key</string>
</resources> </resources>

View file

@ -195,4 +195,5 @@
<string name="picture_in_picture">Bilde i bilde</string> <string name="picture_in_picture">Bilde i bilde</string>
<string name="continue_watching">Fortsett å sjå</string> <string name="continue_watching">Fortsett å sjå</string>
<string name="reload_error">Prøv tilkopling på nytt…</string> <string name="reload_error">Prøv tilkopling på nytt…</string>
<string name="hide_player_control_names_key" translatable="false">hide_player_control_names_key</string>
</resources> </resources>

View file

@ -538,4 +538,5 @@
<string name="use">Bruk</string> <string name="use">Bruk</string>
<string name="help">Hjelp</string> <string name="help">Hjelp</string>
<string name="profile_background_des">Profilbakgrunn</string> <string name="profile_background_des">Profilbakgrunn</string>
<string name="hide_player_control_names_key" translatable="false">hide_player_control_names_key</string>
</resources> </resources>

View file

@ -159,4 +159,5 @@
<string name="no_data">କୌଣସି ତଥ୍ୟ ନାହିଁ</string> <string name="no_data">କୌଣସି ତଥ୍ୟ ନାହିଁ</string>
<string name="app_dub_sub_episode_text_format" formatted="true">%1$s ଅ %2$d</string> <string name="app_dub_sub_episode_text_format" formatted="true">%1$s ଅ %2$d</string>
<string name="video_skip_op">ଆଦ୍ୟ ବାଦ୍ ଦିଅ</string> <string name="video_skip_op">ଆଦ୍ୟ ବାଦ୍ ଦିଅ</string>
<string name="hide_player_control_names_key" translatable="false">hide_player_control_names_key</string>
</resources> </resources>

View file

@ -635,4 +635,5 @@
<string name="dismiss">Odrzuć</string> <string name="dismiss">Odrzuć</string>
<string name="open_downloaded_repo">Otwórz repozytorium</string> <string name="open_downloaded_repo">Otwórz repozytorium</string>
<string name="device_pin_url_message">Odwiedź <b>%s</b> na swoim smartfonie lub komputerze i wprowadź powyższy kod</string> <string name="device_pin_url_message">Odwiedź <b>%s</b> na swoim smartfonie lub komputerze i wprowadź powyższy kod</string>
<string name="hide_player_control_names_key" translatable="false">hide_player_control_names_key</string>
</resources> </resources>

View file

@ -621,4 +621,5 @@
<string name="player_settings_play_in_fcast">Fcast</string> <string name="player_settings_play_in_fcast">Fcast</string>
<string name="player_settings_select_cast_device">Escolha o dispositivo</string> <string name="player_settings_select_cast_device">Escolha o dispositivo</string>
<string name="episode_action_cast_mirror">Transmitir</string> <string name="episode_action_cast_mirror">Transmitir</string>
<string name="hide_player_control_names_key" translatable="false">hide_player_control_names_key</string>
</resources> </resources>

View file

@ -247,4 +247,5 @@
<string name="sort_save">oooooh uuaagh</string> <string name="sort_save">oooooh uuaagh</string>
<string name="action_open_play">@string/home_play</string> <string name="action_open_play">@string/home_play</string>
<string name="chromecast_subtitles_settings">oouuhhh ahhooo-ahah</string> <string name="chromecast_subtitles_settings">oouuhhh ahhooo-ahah</string>
<string name="hide_player_control_names_key" translatable="false">hide_player_control_names_key</string>
</resources> </resources>

View file

@ -641,4 +641,5 @@
<string name="player_settings_select_cast_device">Selectați divece-ul pe care doriți să faceți cast</string> <string name="player_settings_select_cast_device">Selectați divece-ul pe care doriți să faceți cast</string>
<string name="episode_action_cast_mirror">Cast mirror</string> <string name="episode_action_cast_mirror">Cast mirror</string>
<string name="player_settings_play_in_fcast">Fcast</string> <string name="player_settings_play_in_fcast">Fcast</string>
<string name="hide_player_control_names_key" translatable="false">hide_player_control_names_key</string>
</resources> </resources>

View file

@ -622,4 +622,5 @@
<string name="episode_upcoming_format" formatted="true">Выйдет %s</string> <string name="episode_upcoming_format" formatted="true">Выйдет %s</string>
<string name="player_settings_play_in_fcast">Fcast</string> <string name="player_settings_play_in_fcast">Fcast</string>
<string name="player_settings_select_cast_device">Выберите девайс для трансляции</string> <string name="player_settings_select_cast_device">Выберите девайс для трансляции</string>
<string name="hide_player_control_names_key" translatable="false">hide_player_control_names_key</string>
</resources> </resources>

View file

@ -377,4 +377,5 @@
<string name="add_repository">Pridať repozitár</string> <string name="add_repository">Pridať repozitár</string>
<string name="repository_name_hint">Názov repozitára</string> <string name="repository_name_hint">Názov repozitára</string>
<string name="view_public_repositories_button">Zobraziť komunitné repozitáre</string> <string name="view_public_repositories_button">Zobraziť komunitné repozitáre</string>
<string name="hide_player_control_names_key" translatable="false">hide_player_control_names_key</string>
</resources> </resources>

View file

@ -485,4 +485,5 @@
<string name="skip_type_op">Bilowga</string> <string name="skip_type_op">Bilowga</string>
<string name="skip_type_mixed_op">Bilow isku qasan</string> <string name="skip_type_mixed_op">Bilow isku qasan</string>
<string name="skip_type_creddits">Qoraalka dhamaadka</string> <string name="skip_type_creddits">Qoraalka dhamaadka</string>
<string name="hide_player_control_names_key" translatable="false">hide_player_control_names_key</string>
</resources> </resources>

View file

@ -626,4 +626,5 @@
<string name="cs3wiki">CloudStream Wiki</string> <string name="cs3wiki">CloudStream Wiki</string>
<string name="pref_category_accounts">Konton</string> <string name="pref_category_accounts">Konton</string>
<string name="pref_category_security">Säkerhet</string> <string name="pref_category_security">Säkerhet</string>
<string name="hide_player_control_names_key" translatable="false">hide_player_control_names_key</string>
</resources> </resources>

View file

@ -615,4 +615,5 @@
<string name="favorite_removed">%கள் பிடித்தவைகளிலிருந்து அகற்றப்பட்டன</string> <string name="favorite_removed">%கள் பிடித்தவைகளிலிருந்து அகற்றப்பட்டன</string>
<string name="biometric_warning">உங்கள் கிளவுட்ச்ட்ரீம் தரவு இப்போது காப்புப் பிரதி எடுக்கப்பட்டுள்ளது. இதன் சாத்தியம் மிகக் குறைவு என்றாலும், எல்லா சாதனங்களும் வித்தியாசமாக நடந்து கொள்ளலாம். அரிய விசயத்தில், பயன்பாட்டை அணுகுவதிலிருந்து நீங்கள் பூட்டப்படுகிறீர்கள், பயன்பாட்டு தரவை முழுவதுமாக அழித்து, காப்புப்பிரதியிலிருந்து மீட்டெடுக்கவும். இதிலிருந்து எழும் ஏதேனும் சிரமத்திற்கு நாங்கள் மிகவும் வருந்துகிறோம்.</string> <string name="biometric_warning">உங்கள் கிளவுட்ச்ட்ரீம் தரவு இப்போது காப்புப் பிரதி எடுக்கப்பட்டுள்ளது. இதன் சாத்தியம் மிகக் குறைவு என்றாலும், எல்லா சாதனங்களும் வித்தியாசமாக நடந்து கொள்ளலாம். அரிய விசயத்தில், பயன்பாட்டை அணுகுவதிலிருந்து நீங்கள் பூட்டப்படுகிறீர்கள், பயன்பாட்டு தரவை முழுவதுமாக அழித்து, காப்புப்பிரதியிலிருந்து மீட்டெடுக்கவும். இதிலிருந்து எழும் ஏதேனும் சிரமத்திற்கு நாங்கள் மிகவும் வருந்துகிறோம்.</string>
<string name="custom_media_singluar">ஊடகம்</string> <string name="custom_media_singluar">ஊடகம்</string>
<string name="hide_player_control_names_key" translatable="false">hide_player_control_names_key</string>
</resources> </resources>

View file

@ -3,4 +3,5 @@
<string name="app_dub_sub_episode_text_format" formatted="true">%1$s ክፋል %2$d</string> <string name="app_dub_sub_episode_text_format" formatted="true">%1$s ክፋል %2$d</string>
<string name="next_episode_format" formatted="true">ክፋል %d በ ላይ ይወጣል</string> <string name="next_episode_format" formatted="true">ክፋል %d በ ላይ ይወጣል</string>
<string name="cast_format" formatted="true">ተዋሳእቲ፡ %s</string> <string name="cast_format" formatted="true">ተዋሳእቲ፡ %s</string>
<string name="hide_player_control_names_key" translatable="false">hide_player_control_names_key</string>
</resources> </resources>

View file

@ -265,4 +265,5 @@
<string name="chromecast_subtitles_settings">Mga Subtitle ng Chromecast</string> <string name="chromecast_subtitles_settings">Mga Subtitle ng Chromecast</string>
<string name="chromecast_subtitles_settings_des">Mga setting ng mga subtitle ng Chromecast</string> <string name="chromecast_subtitles_settings_des">Mga setting ng mga subtitle ng Chromecast</string>
<string name="play_trailer_button">Maglaro ng Trailer</string> <string name="play_trailer_button">Maglaro ng Trailer</string>
<string name="hide_player_control_names_key" translatable="false">hide_player_control_names_key</string>
</resources> </resources>

View file

@ -681,4 +681,5 @@
<string name="device_pin_error_message">Cihaz PIN kodu alınamıyor, yerel kimlik doğrulamayı deneyin</string> <string name="device_pin_error_message">Cihaz PIN kodu alınamıyor, yerel kimlik doğrulamayı deneyin</string>
<string name="device_pin_expired_message">PIN kodunun süresi doldu!</string> <string name="device_pin_expired_message">PIN kodunun süresi doldu!</string>
<string name="device_pin_counter_text">Kodun süresi %1$dm %2$ds içinde doluyor</string> <string name="device_pin_counter_text">Kodun süresi %1$dm %2$ds içinde doluyor</string>
<string name="hide_player_control_names_key" translatable="false">hide_player_control_names_key</string>
</resources> </resources>

View file

@ -634,4 +634,5 @@
<string name="device_pin_counter_text">Термін дії коду закінчується через %1$dхв %2$dс</string> <string name="device_pin_counter_text">Термін дії коду закінчується через %1$dхв %2$dс</string>
<string name="auth_locally">Автентифікація по місцю</string> <string name="auth_locally">Автентифікація по місцю</string>
<string name="dismiss">Відхилити</string> <string name="dismiss">Відхилити</string>
<string name="hide_player_control_names_key" translatable="false">hide_player_control_names_key</string>
</resources> </resources>

View file

@ -617,4 +617,5 @@
<string name="result_search_tooltip">دیگر ایکسٹینشنز میں تلاش کریں</string> <string name="result_search_tooltip">دیگر ایکسٹینشنز میں تلاش کریں</string>
<string name="recommendations_tooltip">سفارشات دکھائیں</string> <string name="recommendations_tooltip">سفارشات دکھائیں</string>
<string name="biometric_warning">آپ کے CloudStream ڈیٹا کا اب بیک اپ لیا گیا ہے۔ اگرچہ اس کا امکان بہت کم ہے، لیکن مختلف ڈیوائس مختلف طریقے سے کام کر سکتے ہیں۔ اگر آپ ایپ تک رسائی حاصل کرنے سے قاصر ہیں تو، ایپ کا ڈیٹا مکمل طور پر صاف کریں اور بیک اپ سے بحال کریں۔ اس سے ہونے والی کسی بھی تکلیف کے لیے ہم بہت معذرت خواہ ہیں۔</string> <string name="biometric_warning">آپ کے CloudStream ڈیٹا کا اب بیک اپ لیا گیا ہے۔ اگرچہ اس کا امکان بہت کم ہے، لیکن مختلف ڈیوائس مختلف طریقے سے کام کر سکتے ہیں۔ اگر آپ ایپ تک رسائی حاصل کرنے سے قاصر ہیں تو، ایپ کا ڈیٹا مکمل طور پر صاف کریں اور بیک اپ سے بحال کریں۔ اس سے ہونے والی کسی بھی تکلیف کے لیے ہم بہت معذرت خواہ ہیں۔</string>
<string name="hide_player_control_names_key" translatable="false">hide_player_control_names_key</string>
</resources> </resources>

View file

@ -643,4 +643,5 @@
<string name="device_pin_url_message">Truy cập <b>%s</b> trên điện thoại hoặc máy tính và nhập mã bên trên</string> <string name="device_pin_url_message">Truy cập <b>%s</b> trên điện thoại hoặc máy tính và nhập mã bên trên</string>
<string name="device_pin_expired_message">Mã PIN đã hết hạn!</string> <string name="device_pin_expired_message">Mã PIN đã hết hạn!</string>
<string name="device_pin_counter_text">Mã sẽ hết hạn trong %1$dm %2$ds</string> <string name="device_pin_counter_text">Mã sẽ hết hạn trong %1$dm %2$ds</string>
<string name="hide_player_control_names_key" translatable="false">hide_player_control_names_key</string>
</resources> </resources>

View file

@ -671,4 +671,5 @@
<string name="battery_dialog_message">為了確保下載與通知已訂閱的電視節目的不間斷CloudStream 需要取得在背景執行的權限。若點選「確定」,將移至「應用程式資訊」,請找到「應用程式電池使用」並將電池用量設置為「無限制」。請注意,取得此權限並不表示 CS3 會明顯增加電池用量,而是只在必要時在背景執行,例如取得通知或使用官方擴充功能下載影片時。若選擇「取消」,您可以稍後在「一般設定」中調整此設定。</string> <string name="battery_dialog_message">為了確保下載與通知已訂閱的電視節目的不間斷CloudStream 需要取得在背景執行的權限。若點選「確定」,將移至「應用程式資訊」,請找到「應用程式電池使用」並將電池用量設置為「無限制」。請注意,取得此權限並不表示 CS3 會明顯增加電池用量,而是只在必要時在背景執行,例如取得通知或使用官方擴充功能下載影片時。若選擇「取消」,您可以稍後在「一般設定」中調整此設定。</string>
<string name="cs3wiki">CloudStream Wiki</string> <string name="cs3wiki">CloudStream Wiki</string>
<string name="biometric_unsupported">此裝置不支援生物特徵認證</string> <string name="biometric_unsupported">此裝置不支援生物特徵認證</string>
<string name="hide_player_control_names_key" translatable="false">hide_player_control_names_key</string>
</resources> </resources>

View file

@ -673,4 +673,5 @@
<string name="player_settings_select_cast_device">选择投射设备</string> <string name="player_settings_select_cast_device">选择投射设备</string>
<string name="next_season_episode_format" formatted="true">%1$d季%2$d集将在</string> <string name="next_season_episode_format" formatted="true">%1$d季%2$d集将在</string>
<string name="episode_action_cast_mirror">投射镜像</string> <string name="episode_action_cast_mirror">投射镜像</string>
<string name="hide_player_control_names_key" translatable="false">hide_player_control_names_key</string>
</resources> </resources>

View file

@ -96,6 +96,7 @@
<string name="home_next_random_img_des">Next Random</string> <string name="home_next_random_img_des">Next Random</string>
<string name="episode_play_img_des" translatable="false">@string/play_episode</string> <string name="episode_play_img_des" translatable="false">@string/play_episode</string>
<string name="go_back_img_des">Go back</string> <string name="go_back_img_des">Go back</string>
<string name="play_from_beginning_img_des">Play from the Beginning</string>
<string name="change_providers_img_des" translatable="false">@string/home_change_provider_img_des</string> <string name="change_providers_img_des" translatable="false">@string/home_change_provider_img_des</string>
<string name="home_change_provider_img_des">Change Provider</string> <string name="home_change_provider_img_des">Change Provider</string>
<string name="preview_background_img_des">Preview Background</string> <string name="preview_background_img_des">Preview Background</string>
@ -806,4 +807,6 @@
<string name="device_pin_error_message">Can\'t get the device PIN code, try local authentication</string> <string name="device_pin_error_message">Can\'t get the device PIN code, try local authentication</string>
<string name="device_pin_expired_message">PIN code is now expired !</string> <string name="device_pin_expired_message">PIN code is now expired !</string>
<string name="device_pin_counter_text">Code expires in %1$dm %2$ds</string> <string name="device_pin_counter_text">Code expires in %1$dm %2$ds</string>
<string name="hide_player_control_names_key" translatable="false">hide_player_control_names_key</string>
<string name="hide_player_control_names">Hide names of the player\'s controls</string>
</resources> </resources>

View file

@ -37,6 +37,12 @@
android:icon="@drawable/ic_baseline_text_format_24" android:icon="@drawable/ic_baseline_text_format_24"
android:key="@string/prefer_limit_title_rez_key" android:key="@string/prefer_limit_title_rez_key"
android:title="@string/limit_title_rez" /> android:title="@string/limit_title_rez" />
<SwitchPreference
android:icon="@drawable/ic_baseline_text_format_24"
android:key="@string/hide_player_control_names_key"
android:title="@string/hide_player_control_names"
android:defaultValue="false"
/>
</PreferenceCategory> </PreferenceCategory>
<PreferenceCategory <PreferenceCategory

View file

@ -1700,7 +1700,17 @@ suspend fun MainAPI.newMovieLoadResponse(
builder.initializer() builder.initializer()
return builder return builder
} }
/** Episode information that will be passed to LoadLinks function & showed on UI
* @property data string used as main LoadLinks fun parameter.
* @property name Name of the Episode.
* @property season Season number.
* @property episode Episode number.
* @property posterUrl URL of Episode's poster image.
* @property rating Episode rating.
* @property date Episode air date, see addDate.
* @property runTime Episode runtime in seconds.
* @see[addDate]
* */
data class Episode( data class Episode(
var data: String, var data: String,
var name: String? = null, var name: String? = null,
@ -1710,7 +1720,25 @@ data class Episode(
var rating: Int? = null, var rating: Int? = null,
var description: String? = null, var description: String? = null,
var date: Long? = null, var date: Long? = null,
) var runTime: Int? = null,
) {
/**
* Secondary constructor for backwards compatibility without runTime.
* TODO Remove this constructor after there is a new stable release and extensions are updated to support runTime.
*/
constructor(
data: String,
name: String? = null,
season: Int? = null,
episode: Int? = null,
posterUrl: String? = null,
rating: Int? = null,
description: String? = null,
date: Long? = null,
) : this(
data, name, season, episode, posterUrl, rating, description, date, null
)
}
fun Episode.addDate(date: String?, format: String = "yyyy-MM-dd") { fun Episode.addDate(date: String?, format: String = "yyyy-MM-dd") {
try { try {

View file

@ -3,98 +3,251 @@ package com.lagradost.cloudstream3.extractors
import com.lagradost.cloudstream3.SubtitleFile import com.lagradost.cloudstream3.SubtitleFile
import com.lagradost.cloudstream3.amap import com.lagradost.cloudstream3.amap
import com.lagradost.cloudstream3.app import com.lagradost.cloudstream3.app
import com.lagradost.cloudstream3.utils.* import com.lagradost.cloudstream3.utils.ExtractorApi
import kotlinx.coroutines.delay import com.lagradost.cloudstream3.utils.ExtractorLink
import java.net.URI import com.lagradost.cloudstream3.utils.Qualities
import com.lagradost.cloudstream3.utils.loadExtractor
import java.util.Base64
class VidSrcExtractor2 : VidSrcExtractor() { class VidSrcExtractor2 : VidSrcExtractor() {
override val mainUrl = "https://vidsrc.me/embed" override val mainUrl = "https://vidsrc.me"
override suspend fun getUrl(
url: String,
referer: String?,
subtitleCallback: (SubtitleFile) -> Unit,
callback: (ExtractorLink) -> Unit
) {
val newUrl = url.lowercase().replace(mainUrl, super.mainUrl)
super.getUrl(newUrl, referer, subtitleCallback, callback)
}
} }
open class VidSrcExtractor : ExtractorApi() { open class VidSrcExtractor : ExtractorApi() {
override val name = "VidSrc" override val name = "VidSrc"
private val absoluteUrl = "https://v2.vidsrc.me" override val mainUrl = "https://vidsrc.net"
override val mainUrl = "$absoluteUrl/embed" private val apiUrl = "https://vidsrc.stream"
override val requiresReferer = false override val requiresReferer = false
companion object {
/** Infinite function to validate the vidSrc pass */
suspend fun validatePass(url: String) {
val uri = URI(url)
val host = uri.host
// Basically turn https://tm3p.vidsrc.stream/ -> https://vidsrc.stream/
val referer = host.split(".").let {
val size = it.size
"https://" + it.subList(maxOf(0, size - 2), size).joinToString(".") + "/"
}
while (true) {
app.get(url, referer = referer)
delay(60_000)
}
}
}
override suspend fun getUrl( override suspend fun getUrl(
url: String, url: String,
referer: String?, referer: String?,
subtitleCallback: (SubtitleFile) -> Unit, subtitleCallback: (SubtitleFile) -> Unit,
callback: (ExtractorLink) -> Unit callback: (ExtractorLink) -> Unit
) { ) {
val iframedoc = app.get(url).document val iframedoc = app.get(url).document
val serverslist = val srcrcpList =
iframedoc.select("div#sources.button_content div#content div#list div").map { iframedoc.select("div.serversList > div.server").mapNotNull {
val datahash = it.attr("data-hash") val datahash = it.attr("data-hash") ?: return@mapNotNull null
if (datahash.isNotBlank()) { val rcpLink = "$apiUrl/rcp/$datahash"
val links = try { val rcpRes = app.get(rcpLink, referer = apiUrl).text
app.get( val srcrcpLink =
"$absoluteUrl/srcrcp/$datahash", Regex("src:\\s*'(.*)',").find(rcpRes)?.destructured?.component1()
referer = "https://rcp.vidsrc.me/" ?: return@mapNotNull null
).url "https:$srcrcpLink"
} catch (e: Exception) { }
""
}
links
} else ""
}
serverslist.amap { server -> srcrcpList.amap { server ->
val linkfixed = server.replace("https://vidsrc.xyz/", "https://embedsito.com/") val res = app.get(server, referer = apiUrl)
if (linkfixed.contains("/prorcp")) { if (res.url.contains("/prorcp")) {
val srcresponse = app.get(server, referer = absoluteUrl).text val encodedElement = res.document.select("div#reporting_content+div")
val m3u8Regex = Regex("((https:|http:)//.*\\.m3u8)") val decodedUrl =
val srcm3u8 = m3u8Regex.find(srcresponse)?.value ?: return@amap decodeUrl(encodedElement.attr("id"), encodedElement.text()) ?: return@amap
val passRegex = Regex("""['"](.*set_pass[^"']*)""")
val pass = passRegex.find(srcresponse)?.groupValues?.get(1)?.replace(
Regex("""^//"""), "https://"
)
callback.invoke( callback.invoke(
ExtractorLink( ExtractorLink(
this.name, this.name,
this.name, this.name,
srcm3u8, decodedUrl,
"https://vidsrc.stream/", apiUrl,
Qualities.Unknown.value, Qualities.Unknown.value,
extractorData = pass, isM3u8 = true
isM3u8 = true )
)
) )
} else { } else {
loadExtractor(linkfixed, url, subtitleCallback, callback) loadExtractor(res.url, url, subtitleCallback, callback)
} }
} }
} }
} private fun decodeUrl(encType: String, url: String): String? {
return when (encType) {
"NdonQLf1Tzyx7bMG" -> bMGyx71TzQLfdonN(url)
"sXnL9MQIry" -> Iry9MQXnLs(url)
"IhWrImMIGL" -> IGLImMhWrI(url)
"xTyBxQyGTA" -> GTAxQyTyBx(url)
"ux8qjPHC66" -> C66jPHx8qu(url)
"eSfH1IRMyL" -> MyL1IRSfHe(url)
"KJHidj7det" -> detdj7JHiK(url)
"o2VSUnjnZl" -> nZlUnj2VSo(url)
"Oi3v1dAlaM" -> laM1dAi3vO(url)
"TsA2KGDGux" -> GuxKGDsA2T(url)
"JoAHUMCLXV" -> LXVUMCoAHJ(url)
else -> null
}
}
private fun bMGyx71TzQLfdonN(a: String): String {
val b = 3
val c = mutableListOf<String>()
var d = 0
while (d < a.length) {
c.add(a.substring(d, minOf(d + b, a.length)))
d += b
}
val e = c.reversed().joinToString("")
return e
}
private fun Iry9MQXnLs(a: String): String {
val b = "pWB9V)[*4I`nJpp?ozyB~dbr9yt!_n4u"
val d = a.chunked(2).map { it.toInt(16).toChar() }.joinToString("")
var c = ""
for (e in d.indices) {
c += (d[e].code xor b[e % b.length].code).toChar()
}
var e = ""
for (ch in c) {
e += (ch.code - 3).toChar()
}
return String(Base64.getDecoder().decode(e))
}
private fun IGLImMhWrI(a: String): String {
val b = a.reversed()
val c =
b
.map {
when (it) {
in 'a'..'m', in 'A'..'M' -> it + 13
in 'n'..'z', in 'N'..'Z' -> it - 13
else -> it
}
}
.joinToString("")
val d = c.reversed()
return String(Base64.getDecoder().decode(d))
}
private fun GTAxQyTyBx(a: String): String {
val b = a.reversed()
val c = b.filterIndexed { index, _ -> index % 2 == 0 }
return String(Base64.getDecoder().decode(c))
}
private fun C66jPHx8qu(a: String): String {
val b = a.reversed()
val c = "X9a(O;FMV2-7VO5x;Ao:dN1NoFs?j,"
val d = b.chunked(2).map { it.toInt(16).toChar() }.joinToString("")
var e = ""
for (i in d.indices) {
e += (d[i].code xor c[i % c.length].code).toChar()
}
return e
}
private fun MyL1IRSfHe(a: String): String {
val b = a.reversed()
val c = b.map { (it.code - 1).toChar() }.joinToString("")
val d = c.chunked(2).map { it.toInt(16).toChar() }.joinToString("")
return d
}
private fun detdj7JHiK(a: String): String {
val b = a.substring(10, a.length - 16)
val c = "3SAY~#%Y(V%>5d/Yg\"\$G[Lh1rK4a;7ok"
val d = String(Base64.getDecoder().decode(b))
val e = c.repeat((d.length + c.length - 1) / c.length).substring(0, d.length)
var f = ""
for (i in d.indices) {
f += (d[i].code xor e[i].code).toChar()
}
return f
}
private fun nZlUnj2VSo(a: String): String {
val b =
mapOf(
'x' to 'a',
'y' to 'b',
'z' to 'c',
'a' to 'd',
'b' to 'e',
'c' to 'f',
'd' to 'g',
'e' to 'h',
'f' to 'i',
'g' to 'j',
'h' to 'k',
'i' to 'l',
'j' to 'm',
'k' to 'n',
'l' to 'o',
'm' to 'p',
'n' to 'q',
'o' to 'r',
'p' to 's',
'q' to 't',
'r' to 'u',
's' to 'v',
't' to 'w',
'u' to 'x',
'v' to 'y',
'w' to 'z',
'X' to 'A',
'Y' to 'B',
'Z' to 'C',
'A' to 'D',
'B' to 'E',
'C' to 'F',
'D' to 'G',
'E' to 'H',
'F' to 'I',
'G' to 'J',
'H' to 'K',
'I' to 'L',
'J' to 'M',
'K' to 'N',
'L' to 'O',
'M' to 'P',
'N' to 'Q',
'O' to 'R',
'P' to 'S',
'Q' to 'T',
'R' to 'U',
'S' to 'V',
'T' to 'W',
'U' to 'X',
'V' to 'Y',
'W' to 'Z'
)
return a.map { b[it] ?: it }.joinToString("")
}
private fun laM1dAi3vO(a: String): String {
val b = a.reversed()
val c = b.replace("-", "+").replace("_", "/")
val d = String(Base64.getDecoder().decode(c))
var e = ""
val f = 5
for (ch in d) {
e += (ch.code - f).toChar()
}
return e
}
private fun GuxKGDsA2T(a: String): String {
val b = a.reversed()
val c = b.replace("-", "+").replace("_", "/")
val d = String(Base64.getDecoder().decode(c))
var e = ""
val f = 7
for (ch in d) {
e += (ch.code - f).toChar()
}
return e
}
private fun LXVUMCoAHJ(a: String): String {
val b = a.reversed()
val c = b.replace("-", "+").replace("_", "/")
val d = String(Base64.getDecoder().decode(c))
var e = ""
val f = 3
for (ch in d) {
e += (ch.code - f).toChar()
}
return e
}
}

View file

@ -40,7 +40,7 @@ class VidSrcTo : ExtractorApi() {
val finalUrl = DecryptUrl(embedRes.result.encUrl) val finalUrl = DecryptUrl(embedRes.result.encUrl)
if(finalUrl.equals(embedRes.result.encUrl)) return@amap if(finalUrl.equals(embedRes.result.encUrl)) return@amap
when (source.title) { when (source.title) {
"Vidplay" -> AnyVidplay(finalUrl.substringBefore("/e/")).getUrl(finalUrl, referer, subtitleCallback, callback) "F2Cloud" -> AnyVidplay(finalUrl.substringBefore("/e/")).getUrl(finalUrl, referer, subtitleCallback, callback)
"Filemoon" -> FileMoon().getUrl(finalUrl, referer, subtitleCallback, callback) "Filemoon" -> FileMoon().getUrl(finalUrl, referer, subtitleCallback, callback)
} }
} catch (e: Exception) { } catch (e: Exception) {

View file

@ -4,13 +4,14 @@ import com.fasterxml.jackson.annotation.JsonProperty
import com.lagradost.cloudstream3.ErrorLoadingException import com.lagradost.cloudstream3.ErrorLoadingException
import com.lagradost.cloudstream3.SubtitleFile import com.lagradost.cloudstream3.SubtitleFile
import com.lagradost.cloudstream3.app import com.lagradost.cloudstream3.app
import com.lagradost.cloudstream3.base64Encode import com.lagradost.cloudstream3.utils.AppUtils.tryParseJson
import com.lagradost.cloudstream3.utils.ExtractorApi import com.lagradost.cloudstream3.utils.ExtractorApi
import com.lagradost.cloudstream3.utils.ExtractorLink import com.lagradost.cloudstream3.utils.ExtractorLink
import com.lagradost.cloudstream3.utils.M3u8Helper import com.lagradost.cloudstream3.utils.M3u8Helper
import java.net.URLDecoder
import javax.crypto.Cipher import javax.crypto.Cipher
import javax.crypto.spec.SecretKeySpec import javax.crypto.spec.SecretKeySpec
import kotlin.run import kotlin.io.encoding.Base64
// Code found in https://github.com/KillerDogeEmpire/vidplay-keys // Code found in https://github.com/KillerDogeEmpire/vidplay-keys
// special credits to @KillerDogeEmpire for providing key // special credits to @KillerDogeEmpire for providing key
@ -33,6 +34,7 @@ class VidplayOnline : Vidplay() {
override val mainUrl = "https://vidplay.online" override val mainUrl = "https://vidplay.online"
} }
@OptIn(kotlin.io.encoding.ExperimentalEncodingApi::class)
open class Vidplay : ExtractorApi() { open class Vidplay : ExtractorApi() {
override val name = "Vidplay" override val name = "Vidplay"
override val mainUrl = "https://vidplay.site" override val mainUrl = "https://vidplay.site"
@ -58,83 +60,64 @@ open class Vidplay : ExtractorApi() {
} }
override suspend fun getUrl( override suspend fun getUrl(
url: String, url: String,
referer: String?, referer: String?,
subtitleCallback: (SubtitleFile) -> Unit, subtitleCallback: (SubtitleFile) -> Unit,
callback: (ExtractorLink) -> Unit callback: (ExtractorLink) -> Unit
) { ) {
val myKeys = getKeys()
val domain = url.substringBefore("/e/")
val id = url.substringBefore("?").substringAfterLast("/") val id = url.substringBefore("?").substringAfterLast("/")
val encodeId = encodeId(id, getKeys()) val encodedId = encode(id, myKeys.get(0))
val mediaUrl = callFutoken(encodeId, url) val t = url.substringAfter("t=").substringBefore("&")
val res = app.get( val h = encode(id, myKeys.get(1))
"$mediaUrl", headers = mapOf( val mediaUrl = "$domain/mediainfo/$encodedId?t=$t&h=$h"
"Accept" to "application/json, text/javascript, */*; q=0.01", val encodedRes =
"X-Requested-With" to "XMLHttpRequest", app.get("$mediaUrl").parsedSafe<Response>()?.result
), referer = url ?: throw Exception("Unable to fetch link")
).parsedSafe<Response>()?.result val decodedRes = decode(encodedRes, myKeys.get(2))
val res = tryParseJson<Result>(decodedRes)
res?.sources?.map { res?.sources?.map {
M3u8Helper.generateM3u8( M3u8Helper.generateM3u8(this.name, it.file ?: return@map, "$mainUrl/").forEach(callback)
this.name,
it.file ?: return@map,
"$mainUrl/"
).forEach(callback)
} }
res?.tracks?.filter { it.kind == "captions" }?.map { res?.tracks?.filter { it.kind == "captions" }?.map {
subtitleCallback.invoke( subtitleCallback.invoke(SubtitleFile(it.label ?: return@map, it.file ?: return@map))
SubtitleFile(it.label ?: return@map, it.file ?: return@map)
)
} }
} }
private suspend fun callFutoken(id: String, url: String): String? { private fun encode(input: String, key: String): String {
val script = app.get("$mainUrl/futoken", referer = url).text val rc4Key = SecretKeySpec(key.toByteArray(Charsets.UTF_8), "RC4")
val k = "k='(\\S+)'".toRegex().find(script)?.groupValues?.get(1) ?: return null val cipher = Cipher.getInstance("RC4")
val a = mutableListOf(k) cipher.init(Cipher.ENCRYPT_MODE, rc4Key)
for (i in id.indices) { val encryptedBytes = cipher.doFinal(input.toByteArray(Charsets.UTF_8))
a.add((k[i % k.length].code + id[i].code).toString()) return Base64.UrlSafe.encode(encryptedBytes)
}
return "$mainUrl/mediainfo/${a.joinToString(",")}?${url.substringAfter("?")}"
} }
private fun encodeId(id: String, keyList: List<String>): String { fun decode(input: String, key: String): String {
val cipher1 = Cipher.getInstance("RC4") val decodedBytes = Base64.UrlSafe.decode(input)
val cipher2 = Cipher.getInstance("RC4") val rc4Key = SecretKeySpec(key.toByteArray(Charsets.UTF_8), "RC4")
cipher1.init( val cipher = Cipher.getInstance("RC4")
Cipher.DECRYPT_MODE, cipher.init(Cipher.DECRYPT_MODE, rc4Key)
SecretKeySpec(keyList[0].toByteArray(), "RC4"), val decryptedBytes = cipher.doFinal(decodedBytes)
cipher1.parameters val decodedString = String(decryptedBytes, Charsets.UTF_8)
) return URLDecoder.decode(decodedString, "UTF-8")
cipher2.init(
Cipher.DECRYPT_MODE,
SecretKeySpec(keyList[1].toByteArray(), "RC4"),
cipher2.parameters
)
var input = id.toByteArray()
input = cipher1.doFinal(input)
input = cipher2.doFinal(input)
return base64Encode(input).replace("/", "_")
} }
data class Tracks( data class Tracks(
@JsonProperty("file") val file: String? = null, @JsonProperty("file") val file: String? = null,
@JsonProperty("label") val label: String? = null, @JsonProperty("label") val label: String? = null,
@JsonProperty("kind") val kind: String? = null, @JsonProperty("kind") val kind: String? = null,
) )
data class Sources( data class Sources(
@JsonProperty("file") val file: String? = null, @JsonProperty("file") val file: String? = null,
) )
data class Result( data class Result(
@JsonProperty("sources") val sources: ArrayList<Sources>? = arrayListOf(), @JsonProperty("sources") val sources: ArrayList<Sources>? = arrayListOf(),
@JsonProperty("tracks") val tracks: ArrayList<Tracks>? = arrayListOf(), @JsonProperty("tracks") val tracks: ArrayList<Tracks>? = arrayListOf(),
)
data class Response(
@JsonProperty("result") val result: Result? = null,
) )
data class Response(@JsonProperty("result") val result: String? = null)
} }