mirror of
https://github.com/recloudstream/cloudstream.git
synced 2024-08-15 01:53:11 +00:00
Merge branch 'recloudstream:master' into initial-multi-delete
This commit is contained in:
commit
7214c9ff14
86 changed files with 922 additions and 269 deletions
|
@ -60,8 +60,8 @@ android {
|
|||
minSdk = 21
|
||||
targetSdk = 33 /* Android 14 is Fu*ked
|
||||
^ https://developer.android.com/about/versions/14/behavior-changes-14#safer-dynamic-code-loading*/
|
||||
versionCode = 63
|
||||
versionName = "4.3.2"
|
||||
versionCode = 64
|
||||
versionName = "4.4.0"
|
||||
|
||||
resValue("string", "app_version", "${defaultConfig.versionName}${versionNameSuffix ?: ""}")
|
||||
resValue("string", "commit_hash", "git rev-parse --short HEAD".execute() ?: "")
|
||||
|
@ -200,7 +200,7 @@ dependencies {
|
|||
// PlayBack
|
||||
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.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 */
|
||||
implementation("com.github.albfernandez:juniversalchardet:2.5.0") // Subtitle Decoding
|
||||
|
||||
|
|
|
@ -154,7 +154,7 @@ class ExampleInstrumentedTest {
|
|||
fun providerCorrectHomepage() {
|
||||
runBlocking {
|
||||
getAllProviders().toList().amap { api ->
|
||||
TestingUtils.testHomepage(api, ::println)
|
||||
TestingUtils.testHomepage(api, TestingUtils.Logger())
|
||||
}
|
||||
}
|
||||
println("Done providerCorrectHomepage")
|
||||
|
@ -166,7 +166,6 @@ class ExampleInstrumentedTest {
|
|||
TestingUtils.getDeferredProviderTests(
|
||||
this,
|
||||
getAllProviders(),
|
||||
::println
|
||||
) { _, _ -> }
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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.setImage
|
||||
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.search.SearchFragment
|
||||
import com.lagradost.cloudstream3.ui.search.SearchResultBuilder
|
||||
|
@ -1402,7 +1403,7 @@ class MainActivity : AppCompatActivity(), ColorPickerDialogListener, BiometricCa
|
|||
resultviewPreviewMetaDuration.setText(d.durationText)
|
||||
resultviewPreviewMetaRating.setText(d.ratingText)
|
||||
|
||||
resultviewPreviewDescription.setText(d.plotText)
|
||||
resultviewPreviewDescription.setTextHtml(d.plotText)
|
||||
resultviewPreviewPoster.setImage(
|
||||
d.posterImage ?: d.posterBackgroundImage
|
||||
)
|
||||
|
|
|
@ -236,6 +236,7 @@ open class TraktProvider : MainAPI() {
|
|||
posterUrl = fixPath(episode.images?.screenshot?.firstOrNull()),
|
||||
rating = episode.rating?.times(10)?.roundToInt(),
|
||||
description = episode.overview,
|
||||
runTime = episode.runtime
|
||||
).apply {
|
||||
this.addDate(episode.firstAired, "yyyy-MM-dd'T'HH:mm:ss.SSSXXX")
|
||||
if (nextAir == null && this.date != null && this.date!! > unixTimeMS && this.season != 0) {
|
||||
|
|
|
@ -912,7 +912,11 @@ class CS3IPlayer : IPlayer {
|
|||
}
|
||||
|
||||
CSPlayerEvent.SeekForward -> seekTime(seekActionTime, source)
|
||||
|
||||
CSPlayerEvent.SeekBack -> seekTime(-seekActionTime, source)
|
||||
|
||||
CSPlayerEvent.Restart -> seekTo(0, source)
|
||||
|
||||
CSPlayerEvent.NextEpisode -> event(
|
||||
EpisodeSeekEvent(
|
||||
offset = 1,
|
||||
|
|
|
@ -14,20 +14,29 @@ import android.os.Bundle
|
|||
import android.provider.Settings
|
||||
import android.text.Editable
|
||||
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.animation.AlphaAnimation
|
||||
import android.view.animation.Animation
|
||||
import android.view.animation.AnimationUtils
|
||||
import android.widget.LinearLayout
|
||||
import androidx.appcompat.app.AlertDialog
|
||||
import androidx.core.graphics.blue
|
||||
import androidx.core.graphics.green
|
||||
import androidx.core.graphics.red
|
||||
import androidx.core.view.children
|
||||
import androidx.core.view.isGone
|
||||
import androidx.core.view.isInvisible
|
||||
import androidx.core.view.isVisible
|
||||
import androidx.core.widget.doOnTextChanged
|
||||
import androidx.preference.PreferenceManager
|
||||
import com.google.android.material.button.MaterialButton
|
||||
import com.lagradost.cloudstream3.CommonActivity.keyEventListener
|
||||
import com.lagradost.cloudstream3.CommonActivity.playerEventListener
|
||||
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.UserPreferenceDelegate
|
||||
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_VERTICAL_SWIPE = 2.0f // in percentage
|
||||
|
@ -78,6 +91,7 @@ open class FullScreenPlayer : AbstractPlayerFragment() {
|
|||
protected var playerBinding: PlayerCustomLayoutBinding? = null
|
||||
|
||||
private var durationMode: Boolean by UserPreferenceDelegate("duration_mode", false)
|
||||
|
||||
// state of player UI
|
||||
protected var isShowing = false
|
||||
protected var isLocked = false
|
||||
|
@ -109,6 +123,7 @@ open class FullScreenPlayer : AbstractPlayerFragment() {
|
|||
protected var doubleTapPauseEnabled = true
|
||||
protected var playerRotateEnabled = false
|
||||
protected var autoPlayerRotateEnabled = false
|
||||
private var hideControlsNames = false
|
||||
|
||||
protected var subtitleDelay
|
||||
set(value) = try {
|
||||
|
@ -243,7 +258,6 @@ open class FullScreenPlayer : AbstractPlayerFragment() {
|
|||
|
||||
val playerSourceMove = if (isShowing) 0f else -50.toPx.toFloat()
|
||||
|
||||
|
||||
playerBinding?.apply {
|
||||
playerOpenSource.let {
|
||||
ObjectAnimator.ofFloat(it, "translationY", playerSourceMove).apply {
|
||||
|
@ -348,8 +362,7 @@ open class FullScreenPlayer : AbstractPlayerFragment() {
|
|||
if (lockRotation) {
|
||||
if (isLocked) {
|
||||
lockOrientation(this)
|
||||
}
|
||||
else {
|
||||
} else {
|
||||
if (ignoreDynamicOrientation) {
|
||||
// restore when lock is disabled
|
||||
restoreOrientationWithSensor(this)
|
||||
|
@ -728,6 +741,15 @@ open class FullScreenPlayer : AbstractPlayerFragment() {
|
|||
private var currentTapIndex = 0
|
||||
protected fun autoHide() {
|
||||
currentTapIndex++
|
||||
delayHide()
|
||||
}
|
||||
|
||||
override fun playerStatusChanged() {
|
||||
super.playerStatusChanged()
|
||||
delayHide()
|
||||
}
|
||||
|
||||
private fun delayHide() {
|
||||
val index = currentTapIndex
|
||||
playerBinding?.playerHolder?.postDelayed({
|
||||
if (!isCurrentTouchValid && isShowing && index == currentTapIndex && player.getIsPlaying()) {
|
||||
|
@ -949,7 +971,10 @@ open class FullScreenPlayer : AbstractPlayerFragment() {
|
|||
}
|
||||
|
||||
else -> {
|
||||
player.handleEvent(CSPlayerEvent.PlayPauseToggle, PlayerEventSource.UI)
|
||||
player.handleEvent(
|
||||
CSPlayerEvent.PlayPauseToggle,
|
||||
PlayerEventSource.UI
|
||||
)
|
||||
}
|
||||
}
|
||||
} else if (doubleTapEnabled && isFullScreenPlayer) {
|
||||
|
@ -1225,6 +1250,7 @@ open class FullScreenPlayer : AbstractPlayerFragment() {
|
|||
// if nothing has loaded these buttons should not be visible
|
||||
playerBinding?.apply {
|
||||
playerSkipEpisode.isVisible = false
|
||||
playerGoForward.isVisible = false
|
||||
playerTracksBtt.isVisible = false
|
||||
playerSkipOp.isVisible = false
|
||||
shadowOverlay.isVisible = false
|
||||
|
@ -1298,6 +1324,10 @@ open class FullScreenPlayer : AbstractPlayerFragment() {
|
|||
player.handleEvent(CSPlayerEvent.SeekBack)
|
||||
}
|
||||
|
||||
PlayerEventType.Restart -> {
|
||||
player.handleEvent(CSPlayerEvent.Restart)
|
||||
}
|
||||
|
||||
PlayerEventType.ToggleMute -> {
|
||||
player.handleEvent(CSPlayerEvent.ToggleMute)
|
||||
}
|
||||
|
@ -1393,6 +1423,8 @@ open class FullScreenPlayer : AbstractPlayerFragment() {
|
|||
false
|
||||
)
|
||||
|
||||
hideControlsNames = settingsManager.getBoolean(ctx.getString(R.string.hide_player_control_names_key), false)
|
||||
|
||||
val profiles = QualityDataHelper.getProfiles()
|
||||
val type = if (ctx.isUsingMobileData())
|
||||
QualityDataHelper.QualityProfileType.Data
|
||||
|
@ -1413,12 +1445,34 @@ open class FullScreenPlayer : AbstractPlayerFragment() {
|
|||
playerSpeedBtt.isVisible = playBackSpeedEnabled
|
||||
playerResizeBtt.isVisible = playerResizeEnabled
|
||||
playerRotateBtt.isVisible = playerRotateEnabled
|
||||
if (hideControlsNames) {
|
||||
hideControlsNames()
|
||||
}
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
logError(e)
|
||||
}
|
||||
|
||||
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 {
|
||||
autoHide()
|
||||
player.handleEvent(CSPlayerEvent.PlayPauseToggle)
|
||||
|
@ -1462,6 +1516,16 @@ open class FullScreenPlayer : AbstractPlayerFragment() {
|
|||
player.handleEvent(CSPlayerEvent.NextEpisode)
|
||||
}
|
||||
|
||||
playerGoForward.setOnClickListener {
|
||||
autoHide()
|
||||
player.handleEvent(CSPlayerEvent.NextEpisode)
|
||||
}
|
||||
|
||||
playerRestart.setOnClickListener {
|
||||
autoHide()
|
||||
player.handleEvent(CSPlayerEvent.Restart)
|
||||
}
|
||||
|
||||
playerLock.setOnClickListener {
|
||||
autoHide()
|
||||
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) {
|
||||
isVerticalOrientation = height > width
|
||||
updateOrientation()
|
||||
|
|
|
@ -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.result.*
|
||||
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.isLayout
|
||||
import com.lagradost.cloudstream3.ui.subtitles.SUBTITLE_AUTO_SELECT_KEY
|
||||
|
@ -158,6 +159,7 @@ class GeneratorPlayer : FullScreenPlayer() {
|
|||
}
|
||||
|
||||
override fun playerStatusChanged() {
|
||||
super.playerStatusChanged()
|
||||
if (player.getIsPlaying()) {
|
||||
viewModel.forceClearCache = false
|
||||
}
|
||||
|
@ -1096,9 +1098,16 @@ class GeneratorPlayer : FullScreenPlayer() {
|
|||
}
|
||||
|
||||
playerBinding?.playerSkipOp?.isVisible = isOpVisible
|
||||
|
||||
when {
|
||||
isLayout(PHONE) ->
|
||||
playerBinding?.playerSkipEpisode?.isVisible =
|
||||
!isOpVisible && viewModel.hasNextEpisode() == true
|
||||
|
||||
else ->
|
||||
playerBinding?.playerGoForward?.isVisible = viewModel.hasNextEpisode() == true
|
||||
}
|
||||
|
||||
if (percentage >= PRELOAD_NEXT_EPISODE_PERCENTAGE) {
|
||||
viewModel.preLoadNextLinks()
|
||||
}
|
||||
|
@ -1253,7 +1262,7 @@ class GeneratorPlayer : FullScreenPlayer() {
|
|||
fun setPlayerDimen(widthHeight: Pair<Int, Int>?) {
|
||||
val extra = if (widthHeight != null) {
|
||||
val (width, height) = widthHeight
|
||||
"${width}x${height}"
|
||||
"- ${width}x${height}"
|
||||
} else {
|
||||
""
|
||||
}
|
||||
|
@ -1264,7 +1273,7 @@ class GeneratorPlayer : FullScreenPlayer() {
|
|||
0 -> ""
|
||||
1 -> extra
|
||||
2 -> source
|
||||
3 -> "$source - $extra"
|
||||
3 -> "$source $extra"
|
||||
else -> ""
|
||||
}
|
||||
playerBinding?.playerVideoTitleRez?.apply {
|
||||
|
@ -1289,7 +1298,8 @@ class GeneratorPlayer : FullScreenPlayer() {
|
|||
inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?
|
||||
): View? {
|
||||
// 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]
|
||||
sync = ViewModelProvider(this)[SyncViewModel::class.java]
|
||||
|
|
|
@ -26,6 +26,7 @@ enum class PlayerEventType(val value: Int) {
|
|||
Resize(13),
|
||||
SearchSubtitlesOnline(14),
|
||||
SkipOp(15),
|
||||
Restart(16),
|
||||
}
|
||||
|
||||
enum class CSPlayerEvent(val value: Int) {
|
||||
|
@ -39,6 +40,7 @@ enum class CSPlayerEvent(val value: Int) {
|
|||
PrevEpisode(6),
|
||||
PlayPauseToggle(7),
|
||||
ToggleMute(8),
|
||||
Restart(9),
|
||||
}
|
||||
|
||||
enum class CSPlayerLoading {
|
||||
|
|
|
@ -27,7 +27,8 @@ import com.lagradost.cloudstream3.utils.UIHelper.toPx
|
|||
import com.lagradost.cloudstream3.utils.VideoDownloadHelper
|
||||
import java.text.DateFormat
|
||||
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_VLC_PLAYER = 2
|
||||
|
@ -58,6 +59,7 @@ const val ACTION_MARK_AS_WATCHED = 18
|
|||
const val ACTION_FCAST = 19
|
||||
|
||||
const val TV_EP_SIZE = 400
|
||||
|
||||
data class EpisodeClickEvent(val action: Int, val data: ResultEpisode)
|
||||
|
||||
class EpisodeAdapter(
|
||||
|
@ -274,7 +276,10 @@ class EpisodeAdapter(
|
|||
episodeDate.setText(
|
||||
txt(
|
||||
R.string.episode_upcoming_format,
|
||||
secondsToReadable(card.airDate.minus(unixTimeMS).div(1000).toInt(), "")
|
||||
secondsToReadable(
|
||||
card.airDate.minus(unixTimeMS).div(1000).toInt(),
|
||||
""
|
||||
)
|
||||
)
|
||||
)
|
||||
} else {
|
||||
|
@ -292,6 +297,12 @@ class EpisodeAdapter(
|
|||
episodeDate.isVisible = false
|
||||
}
|
||||
|
||||
episodeRuntime.setText(
|
||||
txt(
|
||||
card.runTime?.times(60L)?.toInt()?.let { secondsToReadable(it, "") }
|
||||
)
|
||||
)
|
||||
|
||||
if (isLayout(EMULATOR or PHONE)) {
|
||||
episodePoster.setOnClickListener {
|
||||
clickCallback.invoke(EpisodeClickEvent(ACTION_CLICK_DEFAULT, card))
|
||||
|
|
|
@ -51,6 +51,7 @@ data class ResultEpisode(
|
|||
/** Sum of all previous season episode counts + episode */
|
||||
val totalEpisodeIndex: Int? = null,
|
||||
val airDate: Long? = null,
|
||||
val runTime: Int? = null,
|
||||
)
|
||||
|
||||
fun ResultEpisode.getRealPosition(): Long {
|
||||
|
@ -87,6 +88,7 @@ fun buildResultEpisode(
|
|||
parentId: Int,
|
||||
totalEpisodeIndex: Int? = null,
|
||||
airDate: Long? = null,
|
||||
runTime: Int? = null,
|
||||
): ResultEpisode {
|
||||
val posDur = getViewPos(id)
|
||||
val videoWatchState = getVideoWatchState(id) ?: VideoWatchState.None
|
||||
|
@ -111,6 +113,7 @@ fun buildResultEpisode(
|
|||
videoWatchState,
|
||||
totalEpisodeIndex,
|
||||
airDate,
|
||||
runTime,
|
||||
)
|
||||
}
|
||||
|
||||
|
|
|
@ -2371,7 +2371,8 @@ class ResultViewModel2 : ViewModel() {
|
|||
loadResponse.type,
|
||||
mainId,
|
||||
totalIndex,
|
||||
airDate = i.date
|
||||
airDate = i.date,
|
||||
runTime = i.runTime,
|
||||
)
|
||||
|
||||
val season = eps.seasonIndex ?: 0
|
||||
|
@ -2426,7 +2427,8 @@ class ResultViewModel2 : ViewModel() {
|
|||
loadResponse.type,
|
||||
mainId,
|
||||
totalIndex,
|
||||
airDate = episode.date
|
||||
airDate = episode.date,
|
||||
runTime = episode.runTime,
|
||||
)
|
||||
|
||||
val season = ep.seasonIndex ?: 0
|
||||
|
|
|
@ -87,10 +87,6 @@ class SettingsPlayer : PreferenceFragmentCompat() {
|
|||
return@setOnPreferenceClickListener true
|
||||
}
|
||||
|
||||
/*(getPref(R.string.double_tap_seek_time_key) as? SeekBarPreference?)?.let {
|
||||
|
||||
}*/
|
||||
|
||||
getPref(R.string.prefer_limit_title_rez_key)?.setOnPreferenceClickListener {
|
||||
val prefNames = resources.getStringArray(R.array.limit_title_rez_pref_names)
|
||||
val prefValues = resources.getIntArray(R.array.limit_title_rez_pref_values)
|
||||
|
@ -109,6 +105,8 @@ class SettingsPlayer : PreferenceFragmentCompat() {
|
|||
return@setOnPreferenceClickListener true
|
||||
}
|
||||
|
||||
getPref(R.string.hide_player_control_names_key)?.hideOn(TV)
|
||||
|
||||
getPref(R.string.quality_pref_key)?.setOnPreferenceClickListener {
|
||||
val prefValues = Qualities.values().map { it.value }.reversed().toMutableList()
|
||||
prefValues.remove(Qualities.Unknown.value)
|
||||
|
|
|
@ -67,8 +67,6 @@ object BackupUtils {
|
|||
OPEN_SUBTITLES_USER_KEY,
|
||||
SUBDL_SUBTITLES_USER_KEY,
|
||||
|
||||
DOWNLOAD_EPISODE_CACHE,
|
||||
|
||||
"biometric_key", // can lock down users if backup is shared on a incompatible device
|
||||
"nginx_user", // Nginx user key
|
||||
"download_path_key" // No access rights after restore data from backup
|
||||
|
|
9
app/src/main/res/drawable/ic_baseline_equalizer_24.xml
Normal file
9
app/src/main/res/drawable/ic_baseline_equalizer_24.xml
Normal 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>
|
9
app/src/main/res/drawable/ic_baseline_replay_24.xml
Normal file
9
app/src/main/res/drawable/ic_baseline_replay_24.xml
Normal 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>
|
9
app/src/main/res/drawable/ic_baseline_restart_24.xml
Normal file
9
app/src/main/res/drawable/ic_baseline_restart_24.xml
Normal 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>
|
10
app/src/main/res/drawable/ic_baseline_skip_next_24_big.xml
Normal file
10
app/src/main/res/drawable/ic_baseline_skip_next_24_big.xml
Normal 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>
|
|
@ -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>
|
|
@ -68,7 +68,9 @@
|
|||
android:layout_height="wrap_content"
|
||||
android:layout_margin="5dp"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent">
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
android:visibility="gone"
|
||||
tools:visibility="visible">
|
||||
|
||||
<ImageView
|
||||
android:layout_width="30dp"
|
||||
|
@ -82,12 +84,11 @@
|
|||
android:id="@+id/player_loading_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:focusable="true"
|
||||
android:focusableInTouchMode="true"
|
||||
android:clickable="false"
|
||||
android:focusable="false"
|
||||
android:focusableInTouchMode="false"
|
||||
android:nextFocusRight="@id/overlay_loading_skip_button"
|
||||
android:nextFocusDown="@id/overlay_loading_skip_button" />
|
||||
</FrameLayout>
|
||||
|
|
|
@ -172,27 +172,108 @@
|
|||
android:id="@+id/player_go_back_holder"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_margin="5dp"
|
||||
android:layout_margin="20dp"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent">
|
||||
|
||||
<ImageView
|
||||
android:layout_width="30dp"
|
||||
android:layout_height="30dp"
|
||||
android:layout_gravity="center"
|
||||
android:contentDescription="@string/go_back_img_des"
|
||||
android:src="@drawable/ic_baseline_arrow_back_24"
|
||||
app:tint="@android:color/white" />
|
||||
<LinearLayout
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginEnd="10dp"
|
||||
android:id="@+id/player_go_back_root"
|
||||
android:orientation="vertical">
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/player_go_back"
|
||||
android:layout_width="70dp"
|
||||
android:layout_height="70dp"
|
||||
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: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>
|
||||
|
||||
</FrameLayout>
|
||||
|
||||
|
||||
|
@ -626,7 +707,7 @@
|
|||
android:nextFocusLeft="@id/player_sources_btt"
|
||||
android:nextFocusRight="@id/player_skip_op"
|
||||
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
|
||||
android:id="@+id/player_skip_op"
|
||||
|
@ -641,10 +722,10 @@
|
|||
android:id="@+id/player_skip_episode"
|
||||
style="@style/VideoButton"
|
||||
android:nextFocusLeft="@id/player_skip_op"
|
||||
|
||||
android:nextFocusRight="@id/player_lock"
|
||||
android:text="@string/next_episode"
|
||||
app:icon="@drawable/ic_baseline_skip_next_24" />
|
||||
|
||||
</LinearLayout>
|
||||
</LinearLayout>
|
||||
</HorizontalScrollView>
|
||||
|
|
|
@ -231,7 +231,7 @@
|
|||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginStart="80dp"
|
||||
android:layout_marginStart="32dp"
|
||||
android:layout_marginTop="20dp"
|
||||
android:layout_marginEnd="32dp"
|
||||
android:orientation="vertical">
|
||||
|
@ -240,6 +240,7 @@
|
|||
android:id="@+id/player_video_title"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:textAlignment="viewEnd"
|
||||
android:gravity="end"
|
||||
android:textColor="@color/white"
|
||||
android:textSize="16sp"
|
||||
|
@ -250,6 +251,7 @@
|
|||
android:id="@+id/player_video_title_rez"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:textAlignment="viewEnd"
|
||||
android:gravity="end"
|
||||
android:textColor="@color/white"
|
||||
android:textSize="16sp"
|
||||
|
@ -285,28 +287,116 @@
|
|||
android:id="@+id/player_go_back_holder"
|
||||
android:layout_width="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_constraintTop_toTopOf="parent">
|
||||
|
||||
<ImageView
|
||||
android:layout_width="30dp"
|
||||
android:layout_height="30dp"
|
||||
android:layout_gravity="center"
|
||||
android:contentDescription="@string/go_back_img_des"
|
||||
android:src="@drawable/ic_baseline_arrow_back_24"
|
||||
app:tint="@android:color/white" />
|
||||
<LinearLayout
|
||||
android:layout_width="60dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginEnd="10dp"
|
||||
android:id="@+id/player_go_back_root"
|
||||
android:orientation="vertical">
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/player_go_back"
|
||||
android:layout_width="70dp"
|
||||
android:layout_height="70dp"
|
||||
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>
|
||||
|
||||
</FrameLayout>
|
||||
</FrameLayout>
|
||||
|
||||
|
@ -509,6 +599,7 @@
|
|||
android:layout_height="wrap_content"
|
||||
android:layoutDirection="ltr"
|
||||
android:orientation="horizontal">
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/player_pause_play"
|
||||
|
||||
|
@ -520,14 +611,14 @@
|
|||
android:focusable="true"
|
||||
android:focusableInTouchMode="true"
|
||||
android:nextFocusUp="@id/skip_chapter_button"
|
||||
android:nextFocusDown="@id/player_skip_op"
|
||||
android:nextFocusDown="@id/player_lock_holder"
|
||||
android:src="@drawable/netflix_pause"
|
||||
|
||||
android:tag="@string/tv_no_focus_tag"
|
||||
app:tint="@color/player_button_tv"
|
||||
tools:ignore="ContentDescription"
|
||||
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
|
||||
android:id="@id/exo_position"
|
||||
|
@ -618,10 +709,10 @@
|
|||
android:textColor="@android:color/white"
|
||||
android:textSize="14sp"
|
||||
android:textStyle="normal"
|
||||
android:visibility="gone"
|
||||
app:layout_constraintBaseline_toBaselineOf="@id/exo_position"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
tools:text="-23:20"
|
||||
android:visibility="gone"/>
|
||||
tools:text="-23:20" />
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||
|
||||
|
||||
|
@ -672,12 +763,13 @@
|
|||
android:id="@+id/player_skip_episode"
|
||||
style="@style/VideoButtonTV"
|
||||
android:nextFocusLeft="@id/player_skip_op"
|
||||
|
||||
android:nextFocusRight="@id/player_resize_btt"
|
||||
android:nextFocusUp="@id/player_pause_play"
|
||||
android:nextFocusDown="@id/player_resize_btt"
|
||||
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
|
||||
android:id="@+id/player_resize_btt"
|
||||
|
@ -732,7 +824,7 @@
|
|||
android:nextFocusRight="@id/player_skip_op"
|
||||
android:nextFocusUp="@id/player_pause_play"
|
||||
android:text="@string/tracks"
|
||||
app:icon="@drawable/ic_baseline_playlist_play_24" />
|
||||
app:icon="@drawable/ic_baseline_equalizer_24" />
|
||||
</LinearLayout>
|
||||
</HorizontalScrollView>
|
||||
</LinearLayout>
|
||||
|
|
|
@ -106,19 +106,37 @@
|
|||
tools:text="1. Jobless" />
|
||||
</LinearLayout>
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="start"
|
||||
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
|
||||
android:id="@+id/episode_date"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:textColor="?attr/grayTextColor"
|
||||
tools:text="15 Apr 2024" />
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
<com.lagradost.cloudstream3.ui.download.button.PieFetchButton
|
||||
|
|
|
@ -30,28 +30,27 @@
|
|||
|
||||
<ImageView
|
||||
android:id="@+id/subtitle_offset_subtract_more"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="match_parent"
|
||||
android:layout_width="24dp"
|
||||
android:layout_height="24dp"
|
||||
android:layout_gravity="center"
|
||||
android:layout_weight="1"
|
||||
android:layout_marginStart="10dp"
|
||||
android:layout_marginEnd="10dp"
|
||||
android:background="?android:attr/selectableItemBackgroundBorderless"
|
||||
android:focusable="true"
|
||||
android:nextFocusRight="@id/subtitle_offset_subtract"
|
||||
android:padding="10dp"
|
||||
android:src="@drawable/ic_baseline_keyboard_arrow_left_24"
|
||||
app:tint="?attr/white"
|
||||
tools:ignore="ContentDescription" />
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/subtitle_offset_subtract"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="match_parent"
|
||||
android:layout_width="24dp"
|
||||
android:layout_height="24dp"
|
||||
android:layout_gravity="center"
|
||||
android:layout_weight="1"
|
||||
android:layout_marginEnd="10dp"
|
||||
android:background="?android:attr/selectableItemBackgroundBorderless"
|
||||
android:focusable="true"
|
||||
android:nextFocusLeft="@id/subtitle_offset_subtract_more"
|
||||
android:padding="10dp"
|
||||
android:src="@drawable/baseline_remove_24"
|
||||
app:tint="?attr/white"
|
||||
tools:ignore="ContentDescription" />
|
||||
|
@ -67,29 +66,29 @@
|
|||
|
||||
<ImageView
|
||||
android:id="@+id/subtitle_offset_add"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="match_parent"
|
||||
android:layout_width="24dp"
|
||||
android:layout_height="24dp"
|
||||
android:layout_gravity="center"
|
||||
android:layout_weight="1"
|
||||
android:layout_marginStart="10dp"
|
||||
android:background="?android:attr/selectableItemBackgroundBorderless"
|
||||
android:focusable="true"
|
||||
android:nextFocusRight="@id/subtitle_offset_add_more"
|
||||
android:padding="10dp"
|
||||
android:src="@drawable/ic_baseline_add_24"
|
||||
app:tint="?attr/white"
|
||||
tools:ignore="ContentDescription" />
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/subtitle_offset_add_more"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="match_parent"
|
||||
android:layout_width="24dp"
|
||||
android:layout_height="24dp"
|
||||
android:layout_gravity="center"
|
||||
android:layout_weight="1"
|
||||
android:layout_marginStart="10dp"
|
||||
android:layout_marginEnd="10dp"
|
||||
android:background="?android:attr/selectableItemBackgroundBorderless"
|
||||
android:focusable="true"
|
||||
android:nextFocusLeft="@id/subtitle_offset_add"
|
||||
android:nextFocusDown="@id/apply_btt"
|
||||
android:padding="10dp"
|
||||
|
||||
android:src="@drawable/ic_baseline_keyboard_arrow_right_24"
|
||||
app:tint="?attr/white"
|
||||
tools:ignore="ContentDescription" />
|
||||
|
|
|
@ -137,33 +137,110 @@
|
|||
|
||||
<FrameLayout
|
||||
android:id="@+id/player_go_back_holder"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="0dp"
|
||||
android:layout_margin="5dp"
|
||||
android:clickable="false"
|
||||
android:focusable="false"
|
||||
android:focusableInTouchMode="false"
|
||||
android:visibility="gone"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_margin="20dp"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent">
|
||||
|
||||
<ImageView
|
||||
android:layout_width="30dp"
|
||||
android:layout_height="30dp"
|
||||
android:layout_gravity="center"
|
||||
android:contentDescription="@string/go_back_img_des"
|
||||
android:src="@drawable/ic_baseline_arrow_back_24"
|
||||
app:tint="@android:color/white" />
|
||||
<LinearLayout
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginEnd="10dp"
|
||||
android:id="@+id/player_go_back_root"
|
||||
android:orientation="vertical">
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/player_go_back"
|
||||
android:layout_width="70dp"
|
||||
android:layout_height="70dp"
|
||||
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: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>
|
||||
|
||||
</FrameLayout>
|
||||
</FrameLayout>
|
||||
|
||||
|
|
|
@ -106,4 +106,5 @@
|
|||
<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="subscribe_tooltip">Nuwe episode notifikasie</string>
|
||||
<string name="hide_player_control_names_key" translatable="false">hide_player_control_names_key</string>
|
||||
</resources>
|
||||
|
|
|
@ -640,4 +640,5 @@
|
|||
<string name="dismiss">تجاهل</string>
|
||||
<string name="open_downloaded_repo">متاح الريپوزيتوري</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>
|
||||
|
|
|
@ -108,4 +108,5 @@
|
|||
<string name="action_open_watching">ተጨማሪ መረጃ</string>
|
||||
<string name="search_provider_text_types">ዓይነቶችን በመጠቀም ይፈልጉ</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>
|
||||
|
|
|
@ -666,4 +666,5 @@
|
|||
<string name="device_pin_url_message">قم بزيارة <b>%s</b> على هاتفك الذكي أو جهاز الكمبيوتر وأدخل الرمز أعلاه</string>
|
||||
<string name="device_pin_error_message">لا يمكن الحصول على رمز PIN للجهاز، حاول المصادقة المحلية</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>
|
||||
|
|
|
@ -352,4 +352,5 @@
|
|||
<string name="documentaries_singular">وثائقي</string>
|
||||
<string name="site">موقع</string>
|
||||
<string name="limit_title">عنوان مشغل الفيديو بحد أقصى لعدد الأحرف</string>
|
||||
<string name="hide_player_control_names_key" translatable="false">hide_player_control_names_key</string>
|
||||
</resources>
|
||||
|
|
|
@ -621,4 +621,5 @@
|
|||
<string name="pick_subtitle">ছাবটাইটেল বাছনি কৰক</string>
|
||||
<string name="play_episode">পৰ্ব খেলাওক</string>
|
||||
<string name="sort_apply">প্ৰয়োগ কৰক</string>
|
||||
<string name="hide_player_control_names_key" translatable="false">hide_player_control_names_key</string>
|
||||
</resources>
|
||||
|
|
|
@ -601,4 +601,5 @@
|
|||
<string name="recommendations_tooltip">Покажи предложения</string>
|
||||
<string name="speed_setting_summary">Добавя опция за промяна на скоростта в плеъра</string>
|
||||
<string name="test_extensions_summary">Този тест е направен за програмисти и не проверява работата на никакви добавки.</string>
|
||||
<string name="hide_player_control_names_key" translatable="false">hide_player_control_names_key</string>
|
||||
</resources>
|
||||
|
|
|
@ -359,4 +359,5 @@
|
|||
<string name="account">অ্যাকাউন্ট</string>
|
||||
<string name="logout">প্রস্থান</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>
|
||||
|
|
|
@ -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_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="hide_player_control_names_key" translatable="false">hide_player_control_names_key</string>
|
||||
</resources>
|
||||
|
|
|
@ -658,4 +658,5 @@
|
|||
<string name="pref_category_accounts">Účty</string>
|
||||
<string name="auth_locally">Lokální ověření</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>
|
||||
|
|
|
@ -615,4 +615,5 @@
|
|||
<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_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>
|
||||
|
|
|
@ -625,4 +625,5 @@
|
|||
<string name="biometric_warning">Τα δεδομένα σας στο CloudStream έχουν κάνει back up. Αν και η πιθανότητα είναι πολύ χαμηλή, όλες οι συσκευές συμπεριφέρονται διαφορετικά. Στη σπάνια περίπτωση, που απαγορευτεί η πρόσβασή σας από την εφαρμογή, διαγράψτε τα δεδομένα εφαρμογής και επαναφέρετέ τα από ένα ήδη υπάρχον backup. Συγνώμη για οποιαδήποτε ταλαιπωρία.</string>
|
||||
<string name="pref_category_accounts">Λογαριασμοί</string>
|
||||
<string name="pref_category_security">Ασφάλεια</string>
|
||||
<string name="hide_player_control_names_key" translatable="false">hide_player_control_names_key</string>
|
||||
</resources>
|
||||
|
|
|
@ -127,4 +127,5 @@
|
|||
<string name="downloaded">Elŝutite</string>
|
||||
<string name="downloading">Elŝutante</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>
|
||||
|
|
|
@ -634,4 +634,5 @@
|
|||
<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_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>
|
||||
|
|
|
@ -191,4 +191,5 @@
|
|||
<string name="action_default">پیشفرض</string>
|
||||
<string name="cartoons_singular">کارتون</string>
|
||||
<string name="torrent_singular">تورنت</string>
|
||||
<string name="hide_player_control_names_key" translatable="false">hide_player_control_names_key</string>
|
||||
</resources>
|
||||
|
|
|
@ -1,2 +1,4 @@
|
|||
<?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>
|
||||
|
|
|
@ -620,4 +620,5 @@
|
|||
<string name="biometric_setting">Verrouillage biométrique</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="hide_player_control_names_key" translatable="false">hide_player_control_names_key</string>
|
||||
</resources>
|
||||
|
|
|
@ -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_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="hide_player_control_names_key" translatable="false">hide_player_control_names_key</string>
|
||||
</resources>
|
||||
|
|
|
@ -209,4 +209,5 @@
|
|||
<string name="subs_outline_color">रूपरेखा रंग</string>
|
||||
<string name="subs_subtitle_elevation">उपशीर्षक ऊंचाई</string>
|
||||
<string name="subs_font">अक्षर शैली</string>
|
||||
<string name="hide_player_control_names_key" translatable="false">hide_player_control_names_key</string>
|
||||
</resources>
|
||||
|
|
|
@ -651,4 +651,5 @@
|
|||
<string name="cs3wiki">CloudStream Wiki</string>
|
||||
<string name="pref_category_accounts">Računi</string>
|
||||
<string name="pref_category_security">Sigurnost</string>
|
||||
<string name="hide_player_control_names_key" translatable="false">hide_player_control_names_key</string>
|
||||
</resources>
|
||||
|
|
|
@ -592,4 +592,5 @@
|
|||
<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_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>
|
||||
|
|
|
@ -647,4 +647,5 @@
|
|||
<string name="cs3wiki">CloudStream Wiki</string>
|
||||
<string name="pref_category_security">Keamanan</string>
|
||||
<string name="pref_category_accounts">Akun</string>
|
||||
<string name="hide_player_control_names_key" translatable="false">hide_player_control_names_key</string>
|
||||
</resources>
|
||||
|
|
|
@ -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_expired_message">Il codice PIN è scaduto!</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>
|
||||
|
|
|
@ -550,4 +550,5 @@
|
|||
\nיגרמו לעדיפות הסרטון להיות 10.
|
||||
\n
|
||||
\nשימו לב: אם הסכום הוא 10 או יותר, הנגן ידלג על טעינת הסרטון כאשר הלינק נטען!</string>
|
||||
<string name="hide_player_control_names_key" translatable="false">hide_player_control_names_key</string>
|
||||
</resources>
|
||||
|
|
|
@ -242,4 +242,5 @@
|
|||
<string name="autoplay_next_settings_des">現在のエピソードが終了したら次のエピソードを開始する</string>
|
||||
<string name="subs_hold_to_reset_to_default">長押しするとデフォルトにリセットされます</string>
|
||||
<string name="popup_resume_download">ダウンロードを再開</string>
|
||||
<string name="hide_player_control_names_key" translatable="false">hide_player_control_names_key</string>
|
||||
</resources>
|
||||
|
|
|
@ -130,4 +130,5 @@
|
|||
<string name="swipe_to_change_settings_des">Brightness ಅಥವಾ volume ಬದಲಾಯಿಸಲು ಎಡ ಅಥವಾ ಬಲಭಾಗದಲ್ಲಿ ಮೇಲಕ್ಕೆ ಅಥವಾ ಕೆಳಕ್ಕೆ ಸ್ಲೈಡ್ ಮಾಡಿ</string>
|
||||
<string name="autoplay_next_settings_des">ಈಗಿನ ಎಪಿಸೋಡ್ ಮುಗಿದಾಗ ಮುಂದಿನ ಎಪಿಸೋಡ್ ಅನ್ನು ಪ್ರಾರಂಭಿಸಿ</string>
|
||||
<string name="swipe_to_change_settings">ಸೆಟ್ಟಿಂಗ್ಗಳನ್ನು ಬದಲಾಯಿಸಲು ಸ್ವೈಪ್ ಮಾಡಿ</string>
|
||||
<string name="hide_player_control_names_key" translatable="false">hide_player_control_names_key</string>
|
||||
</resources>
|
||||
|
|
|
@ -634,4 +634,5 @@
|
|||
<string name="enter_pin_with_name" formatted="true">%s의 PIN 입력</string>
|
||||
<string name="action_remove_from_favorites">즐겨찾기에서 제거</string>
|
||||
<string name="episode_action_cast_mirror">캐스트미러</string>
|
||||
<string name="hide_player_control_names_key" translatable="false">hide_player_control_names_key</string>
|
||||
</resources>
|
||||
|
|
|
@ -260,4 +260,5 @@
|
|||
<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="audio_tracks">Garso takelis</string>
|
||||
<string name="hide_player_control_names_key" translatable="false">hide_player_control_names_key</string>
|
||||
</resources>
|
||||
|
|
|
@ -527,4 +527,5 @@
|
|||
<string name="subscription_in_progress_notification">Abonēto šovu atjaunināšana</string>
|
||||
<string name="subscription_list_name">Abonēts</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>
|
||||
|
|
|
@ -624,4 +624,5 @@
|
|||
<string name="clipboard_permission_error">Грешка при пристапот до таблата со исечоци, обидете се повторно.</string>
|
||||
<string name="clipboard_unknown_error">Грешка при копирање, копирајте го logcat и контактирајте со поддршката за апликацијата.</string>
|
||||
<string name="audio_book_singular">Аудио книга</string>
|
||||
<string name="hide_player_control_names_key" translatable="false">hide_player_control_names_key</string>
|
||||
</resources>
|
||||
|
|
|
@ -280,4 +280,5 @@
|
|||
<string name="subs_edge_type">എഡ്ജ് തരം</string>
|
||||
<string name="subs_outline_color">ഔട്ട്ലൈൻ നിറം</string>
|
||||
<string name="subs_background_color">പശ്ചാത്തല നിറം</string>
|
||||
<string name="hide_player_control_names_key" translatable="false">hide_player_control_names_key</string>
|
||||
</resources>
|
||||
|
|
|
@ -57,4 +57,5 @@
|
|||
<string name="sort_close">Tutup</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="hide_player_control_names_key" translatable="false">hide_player_control_names_key</string>
|
||||
</resources>
|
||||
|
|
|
@ -123,4 +123,5 @@
|
|||
<string name="error_bookmarks_text">Bookmarks</string>
|
||||
<string name="action_remove_from_bookmarks">Neħħi</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>
|
||||
|
|
|
@ -550,4 +550,5 @@
|
|||
<string name="already_voted">သင်နဂိုတည်းကသတ်မှတ်ပြီး</string>
|
||||
<string name="select_library">လိုက်ဘရီရွေးချယ်ရန်</string>
|
||||
<string name="open_with">ဖြင့်ဖွင့်မည်</string>
|
||||
<string name="hide_player_control_names_key" translatable="false">hide_player_control_names_key</string>
|
||||
</resources>
|
||||
|
|
|
@ -128,4 +128,5 @@
|
|||
<string name="player_subtitles_settings_des">प्लेयरको उपशीर्षकको सेटिङ</string>
|
||||
<string name="repo_copy_label">रिपोजिटरी को नाम र यूआरएल</string>
|
||||
<string name="toast_copied">कपी गरियो!</string>
|
||||
<string name="hide_player_control_names_key" translatable="false">hide_player_control_names_key</string>
|
||||
</resources>
|
||||
|
|
|
@ -608,4 +608,5 @@
|
|||
<string name="links_reloaded_toast">Link opnieuw geladen</string>
|
||||
<string name="auto_rotate_video">Autoroteer</string>
|
||||
<string name="rotate_video">Roteer</string>
|
||||
<string name="hide_player_control_names_key" translatable="false">hide_player_control_names_key</string>
|
||||
</resources>
|
||||
|
|
|
@ -195,4 +195,5 @@
|
|||
<string name="picture_in_picture">Bilde i bilde</string>
|
||||
<string name="continue_watching">Fortsett å sjå</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>
|
||||
|
|
|
@ -538,4 +538,5 @@
|
|||
<string name="use">Bruk</string>
|
||||
<string name="help">Hjelp</string>
|
||||
<string name="profile_background_des">Profilbakgrunn</string>
|
||||
<string name="hide_player_control_names_key" translatable="false">hide_player_control_names_key</string>
|
||||
</resources>
|
||||
|
|
|
@ -159,4 +159,5 @@
|
|||
<string name="no_data">କୌଣସି ତଥ୍ୟ ନାହିଁ</string>
|
||||
<string name="app_dub_sub_episode_text_format" formatted="true">%1$s ଅ %2$d</string>
|
||||
<string name="video_skip_op">ଆଦ୍ୟ ବାଦ୍ ଦିଅ</string>
|
||||
<string name="hide_player_control_names_key" translatable="false">hide_player_control_names_key</string>
|
||||
</resources>
|
||||
|
|
|
@ -635,4 +635,5 @@
|
|||
<string name="dismiss">Odrzuć</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="hide_player_control_names_key" translatable="false">hide_player_control_names_key</string>
|
||||
</resources>
|
||||
|
|
|
@ -621,4 +621,5 @@
|
|||
<string name="player_settings_play_in_fcast">Fcast</string>
|
||||
<string name="player_settings_select_cast_device">Escolha o dispositivo</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>
|
||||
|
|
|
@ -247,4 +247,5 @@
|
|||
<string name="sort_save">oooooh uuaagh</string>
|
||||
<string name="action_open_play">@string/home_play</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>
|
||||
|
|
|
@ -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="episode_action_cast_mirror">Cast mirror</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>
|
||||
|
|
|
@ -622,4 +622,5 @@
|
|||
<string name="episode_upcoming_format" formatted="true">Выйдет %s</string>
|
||||
<string name="player_settings_play_in_fcast">Fcast</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>
|
||||
|
|
|
@ -377,4 +377,5 @@
|
|||
<string name="add_repository">Pridať repozitár</string>
|
||||
<string name="repository_name_hint">Názov repozitára</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>
|
||||
|
|
|
@ -485,4 +485,5 @@
|
|||
<string name="skip_type_op">Bilowga</string>
|
||||
<string name="skip_type_mixed_op">Bilow isku qasan</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>
|
||||
|
|
|
@ -626,4 +626,5 @@
|
|||
<string name="cs3wiki">CloudStream Wiki</string>
|
||||
<string name="pref_category_accounts">Konton</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>
|
||||
|
|
|
@ -615,4 +615,5 @@
|
|||
<string name="favorite_removed">%கள் பிடித்தவைகளிலிருந்து அகற்றப்பட்டன</string>
|
||||
<string name="biometric_warning">உங்கள் கிளவுட்ச்ட்ரீம் தரவு இப்போது காப்புப் பிரதி எடுக்கப்பட்டுள்ளது. இதன் சாத்தியம் மிகக் குறைவு என்றாலும், எல்லா சாதனங்களும் வித்தியாசமாக நடந்து கொள்ளலாம். அரிய விசயத்தில், பயன்பாட்டை அணுகுவதிலிருந்து நீங்கள் பூட்டப்படுகிறீர்கள், பயன்பாட்டு தரவை முழுவதுமாக அழித்து, காப்புப்பிரதியிலிருந்து மீட்டெடுக்கவும். இதிலிருந்து எழும் ஏதேனும் சிரமத்திற்கு நாங்கள் மிகவும் வருந்துகிறோம்.</string>
|
||||
<string name="custom_media_singluar">ஊடகம்</string>
|
||||
<string name="hide_player_control_names_key" translatable="false">hide_player_control_names_key</string>
|
||||
</resources>
|
||||
|
|
|
@ -3,4 +3,5 @@
|
|||
<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="cast_format" formatted="true">ተዋሳእቲ፡ %s</string>
|
||||
<string name="hide_player_control_names_key" translatable="false">hide_player_control_names_key</string>
|
||||
</resources>
|
||||
|
|
|
@ -265,4 +265,5 @@
|
|||
<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="play_trailer_button">Maglaro ng Trailer</string>
|
||||
<string name="hide_player_control_names_key" translatable="false">hide_player_control_names_key</string>
|
||||
</resources>
|
||||
|
|
|
@ -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_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="hide_player_control_names_key" translatable="false">hide_player_control_names_key</string>
|
||||
</resources>
|
||||
|
|
|
@ -634,4 +634,5 @@
|
|||
<string name="device_pin_counter_text">Термін дії коду закінчується через %1$dхв %2$dс</string>
|
||||
<string name="auth_locally">Автентифікація по місцю</string>
|
||||
<string name="dismiss">Відхилити</string>
|
||||
<string name="hide_player_control_names_key" translatable="false">hide_player_control_names_key</string>
|
||||
</resources>
|
||||
|
|
|
@ -617,4 +617,5 @@
|
|||
<string name="result_search_tooltip">دیگر ایکسٹینشنز میں تلاش کریں</string>
|
||||
<string name="recommendations_tooltip">سفارشات دکھائیں</string>
|
||||
<string name="biometric_warning">آپ کے CloudStream ڈیٹا کا اب بیک اپ لیا گیا ہے۔ اگرچہ اس کا امکان بہت کم ہے، لیکن مختلف ڈیوائس مختلف طریقے سے کام کر سکتے ہیں۔ اگر آپ ایپ تک رسائی حاصل کرنے سے قاصر ہیں تو، ایپ کا ڈیٹا مکمل طور پر صاف کریں اور بیک اپ سے بحال کریں۔ اس سے ہونے والی کسی بھی تکلیف کے لیے ہم بہت معذرت خواہ ہیں۔</string>
|
||||
<string name="hide_player_control_names_key" translatable="false">hide_player_control_names_key</string>
|
||||
</resources>
|
||||
|
|
|
@ -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_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="hide_player_control_names_key" translatable="false">hide_player_control_names_key</string>
|
||||
</resources>
|
||||
|
|
|
@ -671,4 +671,5 @@
|
|||
<string name="battery_dialog_message">為了確保下載與通知已訂閱的電視節目的不間斷,CloudStream 需要取得在背景執行的權限。若點選「確定」,將移至「應用程式資訊」,請找到「應用程式電池使用」並將電池用量設置為「無限制」。請注意,取得此權限並不表示 CS3 會明顯增加電池用量,而是只在必要時在背景執行,例如取得通知或使用官方擴充功能下載影片時。若選擇「取消」,您可以稍後在「一般設定」中調整此設定。</string>
|
||||
<string name="cs3wiki">CloudStream Wiki</string>
|
||||
<string name="biometric_unsupported">此裝置不支援生物特徵認證</string>
|
||||
<string name="hide_player_control_names_key" translatable="false">hide_player_control_names_key</string>
|
||||
</resources>
|
||||
|
|
|
@ -673,4 +673,5 @@
|
|||
<string name="player_settings_select_cast_device">选择投射设备</string>
|
||||
<string name="next_season_episode_format" formatted="true">%1$d季%2$d集将在</string>
|
||||
<string name="episode_action_cast_mirror">投射镜像</string>
|
||||
<string name="hide_player_control_names_key" translatable="false">hide_player_control_names_key</string>
|
||||
</resources>
|
||||
|
|
|
@ -96,6 +96,7 @@
|
|||
<string name="home_next_random_img_des">Next Random</string>
|
||||
<string name="episode_play_img_des" translatable="false">@string/play_episode</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="home_change_provider_img_des">Change Provider</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_expired_message">PIN code is now expired !</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>
|
|
@ -37,6 +37,12 @@
|
|||
android:icon="@drawable/ic_baseline_text_format_24"
|
||||
android:key="@string/prefer_limit_title_rez_key"
|
||||
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
|
||||
|
|
|
@ -1700,7 +1700,17 @@ suspend fun MainAPI.newMovieLoadResponse(
|
|||
builder.initializer()
|
||||
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(
|
||||
var data: String,
|
||||
var name: String? = null,
|
||||
|
@ -1710,7 +1720,25 @@ data class Episode(
|
|||
var rating: Int? = null,
|
||||
var description: String? = 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") {
|
||||
try {
|
||||
|
|
|
@ -3,48 +3,22 @@ package com.lagradost.cloudstream3.extractors
|
|||
import com.lagradost.cloudstream3.SubtitleFile
|
||||
import com.lagradost.cloudstream3.amap
|
||||
import com.lagradost.cloudstream3.app
|
||||
import com.lagradost.cloudstream3.utils.*
|
||||
import kotlinx.coroutines.delay
|
||||
import java.net.URI
|
||||
import com.lagradost.cloudstream3.utils.ExtractorApi
|
||||
import com.lagradost.cloudstream3.utils.ExtractorLink
|
||||
import com.lagradost.cloudstream3.utils.Qualities
|
||||
import com.lagradost.cloudstream3.utils.loadExtractor
|
||||
import java.util.Base64
|
||||
|
||||
class VidSrcExtractor2 : VidSrcExtractor() {
|
||||
override val mainUrl = "https://vidsrc.me/embed"
|
||||
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)
|
||||
}
|
||||
override val mainUrl = "https://vidsrc.me"
|
||||
}
|
||||
|
||||
open class VidSrcExtractor : ExtractorApi() {
|
||||
override val name = "VidSrc"
|
||||
private val absoluteUrl = "https://v2.vidsrc.me"
|
||||
override val mainUrl = "$absoluteUrl/embed"
|
||||
override val mainUrl = "https://vidsrc.net"
|
||||
private val apiUrl = "https://vidsrc.stream"
|
||||
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(
|
||||
url: String,
|
||||
referer: String?,
|
||||
|
@ -53,48 +27,227 @@ open class VidSrcExtractor : ExtractorApi() {
|
|||
) {
|
||||
val iframedoc = app.get(url).document
|
||||
|
||||
val serverslist =
|
||||
iframedoc.select("div#sources.button_content div#content div#list div").map {
|
||||
val datahash = it.attr("data-hash")
|
||||
if (datahash.isNotBlank()) {
|
||||
val links = try {
|
||||
app.get(
|
||||
"$absoluteUrl/srcrcp/$datahash",
|
||||
referer = "https://rcp.vidsrc.me/"
|
||||
).url
|
||||
} catch (e: Exception) {
|
||||
""
|
||||
}
|
||||
links
|
||||
} else ""
|
||||
val srcrcpList =
|
||||
iframedoc.select("div.serversList > div.server").mapNotNull {
|
||||
val datahash = it.attr("data-hash") ?: return@mapNotNull null
|
||||
val rcpLink = "$apiUrl/rcp/$datahash"
|
||||
val rcpRes = app.get(rcpLink, referer = apiUrl).text
|
||||
val srcrcpLink =
|
||||
Regex("src:\\s*'(.*)',").find(rcpRes)?.destructured?.component1()
|
||||
?: return@mapNotNull null
|
||||
"https:$srcrcpLink"
|
||||
}
|
||||
|
||||
serverslist.amap { server ->
|
||||
val linkfixed = server.replace("https://vidsrc.xyz/", "https://embedsito.com/")
|
||||
if (linkfixed.contains("/prorcp")) {
|
||||
val srcresponse = app.get(server, referer = absoluteUrl).text
|
||||
val m3u8Regex = Regex("((https:|http:)//.*\\.m3u8)")
|
||||
val srcm3u8 = m3u8Regex.find(srcresponse)?.value ?: return@amap
|
||||
val passRegex = Regex("""['"](.*set_pass[^"']*)""")
|
||||
val pass = passRegex.find(srcresponse)?.groupValues?.get(1)?.replace(
|
||||
Regex("""^//"""), "https://"
|
||||
)
|
||||
srcrcpList.amap { server ->
|
||||
val res = app.get(server, referer = apiUrl)
|
||||
if (res.url.contains("/prorcp")) {
|
||||
val encodedElement = res.document.select("div#reporting_content+div")
|
||||
val decodedUrl =
|
||||
decodeUrl(encodedElement.attr("id"), encodedElement.text()) ?: return@amap
|
||||
|
||||
callback.invoke(
|
||||
ExtractorLink(
|
||||
this.name,
|
||||
this.name,
|
||||
srcm3u8,
|
||||
"https://vidsrc.stream/",
|
||||
decodedUrl,
|
||||
apiUrl,
|
||||
Qualities.Unknown.value,
|
||||
extractorData = pass,
|
||||
isM3u8 = true
|
||||
)
|
||||
)
|
||||
} 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
|
||||
}
|
||||
}
|
|
@ -40,7 +40,7 @@ class VidSrcTo : ExtractorApi() {
|
|||
val finalUrl = DecryptUrl(embedRes.result.encUrl)
|
||||
if(finalUrl.equals(embedRes.result.encUrl)) return@amap
|
||||
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)
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
|
|
|
@ -4,13 +4,14 @@ import com.fasterxml.jackson.annotation.JsonProperty
|
|||
import com.lagradost.cloudstream3.ErrorLoadingException
|
||||
import com.lagradost.cloudstream3.SubtitleFile
|
||||
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.ExtractorLink
|
||||
import com.lagradost.cloudstream3.utils.M3u8Helper
|
||||
import java.net.URLDecoder
|
||||
import javax.crypto.Cipher
|
||||
import javax.crypto.spec.SecretKeySpec
|
||||
import kotlin.run
|
||||
import kotlin.io.encoding.Base64
|
||||
|
||||
// Code found in https://github.com/KillerDogeEmpire/vidplay-keys
|
||||
// special credits to @KillerDogeEmpire for providing key
|
||||
|
@ -33,6 +34,7 @@ class VidplayOnline : Vidplay() {
|
|||
override val mainUrl = "https://vidplay.online"
|
||||
}
|
||||
|
||||
@OptIn(kotlin.io.encoding.ExperimentalEncodingApi::class)
|
||||
open class Vidplay : ExtractorApi() {
|
||||
override val name = "Vidplay"
|
||||
override val mainUrl = "https://vidplay.site"
|
||||
|
@ -63,59 +65,43 @@ open class Vidplay : ExtractorApi() {
|
|||
subtitleCallback: (SubtitleFile) -> Unit,
|
||||
callback: (ExtractorLink) -> Unit
|
||||
) {
|
||||
val myKeys = getKeys()
|
||||
val domain = url.substringBefore("/e/")
|
||||
val id = url.substringBefore("?").substringAfterLast("/")
|
||||
val encodeId = encodeId(id, getKeys())
|
||||
val mediaUrl = callFutoken(encodeId, url)
|
||||
val res = app.get(
|
||||
"$mediaUrl", headers = mapOf(
|
||||
"Accept" to "application/json, text/javascript, */*; q=0.01",
|
||||
"X-Requested-With" to "XMLHttpRequest",
|
||||
), referer = url
|
||||
).parsedSafe<Response>()?.result
|
||||
|
||||
val encodedId = encode(id, myKeys.get(0))
|
||||
val t = url.substringAfter("t=").substringBefore("&")
|
||||
val h = encode(id, myKeys.get(1))
|
||||
val mediaUrl = "$domain/mediainfo/$encodedId?t=$t&h=$h"
|
||||
val encodedRes =
|
||||
app.get("$mediaUrl").parsedSafe<Response>()?.result
|
||||
?: throw Exception("Unable to fetch link")
|
||||
val decodedRes = decode(encodedRes, myKeys.get(2))
|
||||
val res = tryParseJson<Result>(decodedRes)
|
||||
res?.sources?.map {
|
||||
M3u8Helper.generateM3u8(
|
||||
this.name,
|
||||
it.file ?: return@map,
|
||||
"$mainUrl/"
|
||||
).forEach(callback)
|
||||
M3u8Helper.generateM3u8(this.name, it.file ?: return@map, "$mainUrl/").forEach(callback)
|
||||
}
|
||||
|
||||
res?.tracks?.filter { it.kind == "captions" }?.map {
|
||||
subtitleCallback.invoke(
|
||||
SubtitleFile(it.label ?: return@map, it.file ?: return@map)
|
||||
)
|
||||
subtitleCallback.invoke(SubtitleFile(it.label ?: return@map, it.file ?: return@map))
|
||||
}
|
||||
}
|
||||
|
||||
private fun encode(input: String, key: String): String {
|
||||
val rc4Key = SecretKeySpec(key.toByteArray(Charsets.UTF_8), "RC4")
|
||||
val cipher = Cipher.getInstance("RC4")
|
||||
cipher.init(Cipher.ENCRYPT_MODE, rc4Key)
|
||||
val encryptedBytes = cipher.doFinal(input.toByteArray(Charsets.UTF_8))
|
||||
return Base64.UrlSafe.encode(encryptedBytes)
|
||||
}
|
||||
|
||||
private suspend fun callFutoken(id: String, url: String): String? {
|
||||
val script = app.get("$mainUrl/futoken", referer = url).text
|
||||
val k = "k='(\\S+)'".toRegex().find(script)?.groupValues?.get(1) ?: return null
|
||||
val a = mutableListOf(k)
|
||||
for (i in id.indices) {
|
||||
a.add((k[i % k.length].code + id[i].code).toString())
|
||||
}
|
||||
return "$mainUrl/mediainfo/${a.joinToString(",")}?${url.substringAfter("?")}"
|
||||
}
|
||||
|
||||
private fun encodeId(id: String, keyList: List<String>): String {
|
||||
val cipher1 = Cipher.getInstance("RC4")
|
||||
val cipher2 = Cipher.getInstance("RC4")
|
||||
cipher1.init(
|
||||
Cipher.DECRYPT_MODE,
|
||||
SecretKeySpec(keyList[0].toByteArray(), "RC4"),
|
||||
cipher1.parameters
|
||||
)
|
||||
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("/", "_")
|
||||
fun decode(input: String, key: String): String {
|
||||
val decodedBytes = Base64.UrlSafe.decode(input)
|
||||
val rc4Key = SecretKeySpec(key.toByteArray(Charsets.UTF_8), "RC4")
|
||||
val cipher = Cipher.getInstance("RC4")
|
||||
cipher.init(Cipher.DECRYPT_MODE, rc4Key)
|
||||
val decryptedBytes = cipher.doFinal(decodedBytes)
|
||||
val decodedString = String(decryptedBytes, Charsets.UTF_8)
|
||||
return URLDecoder.decode(decodedString, "UTF-8")
|
||||
}
|
||||
|
||||
data class Tracks(
|
||||
|
@ -133,8 +119,5 @@ open class Vidplay : ExtractorApi() {
|
|||
@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)
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue