Skip to content
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ import android.view.animation.AccelerateDecelerateInterpolator
import android.view.animation.AlphaAnimation
import android.view.animation.Animation
import android.view.animation.AnimationUtils
import android.view.animation.DecelerateInterpolator
import android.widget.LinearLayout
import androidx.annotation.OptIn
import androidx.appcompat.app.AlertDialog
Expand Down Expand Up @@ -97,6 +98,8 @@ import kotlin.math.max
import kotlin.math.min
import kotlin.math.round
import kotlin.math.roundToInt
import com.lagradost.cloudstream3.utils.AppContextUtils.shouldShowPlayerMetadata


// You can zoom out more than 100%, but it will zoom back into 100%
const val MINIMUM_ZOOM = 0.95f
Expand Down Expand Up @@ -133,7 +136,7 @@ open class FullScreenPlayer : AbstractPlayerFragment() {
private var uiShowingBeforeGesture = false
protected var isLocked = false
protected var timestampShowState = false

private var metadataVisibilityToken = 0
protected var hasEpisodes = false
private set
// protected val hasEpisodes
Expand Down Expand Up @@ -235,10 +238,42 @@ open class FullScreenPlayer : AbstractPlayerFragment() {
requestUpdateBrightnessOverlayOnNextLayout()
}
}

return root
}

private fun scheduleMetadataVisibility() {
val metadataScrim = playerBinding?.playerMetadataScrim ?: return
val ctx = metadataScrim.context ?: return

if (!ctx.shouldShowPlayerMetadata()) {
metadataScrim.isVisible = false
metadataVisibilityToken++ // invalidate pending callbacks
return
}

if (isLayout(PHONE)) {
metadataScrim.isVisible = false
metadataVisibilityToken++ // invalidate pending callbacks
return
}

val isPaused = currentPlayerStatus == CSPlayerLoading.IsPaused
metadataScrim.animate().cancel()
val token = ++metadataVisibilityToken

if (isPaused) {
metadataScrim.postDelayed({
if (token != metadataVisibilityToken) return@postDelayed
metadataScrim.isVisible = true
hidePlayerUI()
}, 8000L)

} else {
metadataScrim.isVisible = false
}
}


@SuppressLint("UnsafeOptInUsageError")
override fun playerUpdated(player: Any?) {
super.playerUpdated(player)
Expand Down Expand Up @@ -456,6 +491,12 @@ open class FullScreenPlayer : AbstractPlayerFragment() {
start()
}
}
playerBinding?.playerMetadataScrim?.let {
ObjectAnimator.ofFloat(it, "translationY", 1f).apply {
duration = 200
start()
}
}

val playerBarMove = if (isShowing) 0f else 50.toPx.toFloat()
playerBinding?.bottomPlayerBar?.let {
Expand Down Expand Up @@ -522,7 +563,7 @@ open class FullScreenPlayer : AbstractPlayerFragment() {
override fun subtitlesChanged() {
val tracks = player.getVideoTracks()
val isBuiltinSubtitles = tracks.currentTextTracks.all { track ->
track.sampleMimeType == MimeTypes.APPLICATION_MEDIA3_CUES
track.sampleMimeType == MimeTypes.APPLICATION_MEDIA3_CUES
}
// Subtitle offset is not possible on built-in media3 tracks
playerBinding?.playerSubtitleOffsetBtt?.isGone =
Expand Down Expand Up @@ -1009,7 +1050,6 @@ open class FullScreenPlayer : AbstractPlayerFragment() {
// BOTTOM
playerLockHolder.startAnimation(fadeAnimation)
// player_go_back_holder?.startAnimation(fadeAnimation)

shadowOverlay.isVisible = true
shadowOverlay.startAnimation(fadeAnimation)
}
Expand Down Expand Up @@ -1080,6 +1120,7 @@ open class FullScreenPlayer : AbstractPlayerFragment() {

override fun playerStatusChanged() {
super.playerStatusChanged()
scheduleMetadataVisibility()
delayHide()
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,7 @@ import com.lagradost.cloudstream3.ui.result.EpisodeAdapter
import com.lagradost.cloudstream3.ui.result.FOCUS_SELF
import com.lagradost.cloudstream3.ui.result.ResultEpisode
import com.lagradost.cloudstream3.ui.result.ResultFragment
import com.lagradost.cloudstream3.ui.result.ResultFragment.bindLogo
import com.lagradost.cloudstream3.ui.result.ResultViewModel2
import com.lagradost.cloudstream3.ui.result.SyncViewModel
import com.lagradost.cloudstream3.ui.result.setLinearListLayout
Expand All @@ -95,6 +96,7 @@ import com.lagradost.cloudstream3.ui.settings.Globals.isLayout
import com.lagradost.cloudstream3.ui.subtitles.SUBTITLE_AUTO_SELECT_KEY
import com.lagradost.cloudstream3.ui.subtitles.SubtitlesFragment
import com.lagradost.cloudstream3.ui.subtitles.SubtitlesFragment.Companion.getAutoSelectLanguageTagIETF
import com.lagradost.cloudstream3.utils.AppContextUtils.getShortSeasonText
import com.lagradost.cloudstream3.utils.AppContextUtils.html
import com.lagradost.cloudstream3.utils.AppContextUtils.sortSubs
import com.lagradost.cloudstream3.utils.Coroutines.ioSafe
Expand Down Expand Up @@ -1568,6 +1570,54 @@ class GeneratorPlayer : FullScreenPlayer() {
return
}
loadLink(links.first(), false)
showPlayerMetadata()
}

private fun showPlayerMetadata() {
val overlay = playerBinding?.playerMetadataScrim ?: return

val titleView = overlay.findViewById<TextView>(R.id.player_movie_title)
val logoView = overlay.findViewById<ImageView>(R.id.player_movie_logo)
val metaView = overlay.findViewById<TextView>(R.id.player_movie_meta)
val descView = overlay.findViewById<TextView>(R.id.player_movie_overview)

val load = viewModel.getLoadResponse() ?: return
val episode = currentMeta as? ResultEpisode
titleView.text = load.name

bindLogo(
url = load.logoUrl,
headers = load.posterHeaders,
titleView = titleView,
logoView = logoView
)

val meta = arrayOf(
load.tags?.takeIf { it.isNotEmpty() }?.joinToString(", "),
load.year?.toString(),
if (!load.type.isMovieType())
context?.getShortSeasonText(
episode = episode?.episode,
season = episode?.season
)
else null,
load.score?.let { "⭐ $it" }
).filterNotNull()
.joinToString(" • ")

metaView.text = meta
metaView.isVisible = meta.isNotBlank()


val description = load.plot

if (!description.isNullOrBlank()) {
descView.isVisible = true
descView.text = description
} else {
descView.isVisible = false

}
}

override fun nextEpisode() {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -449,6 +449,14 @@ object AppContextUtils {
return settingsManager.getBoolean(this.getString(R.string.show_trailers_key), true)
}

fun Context.shouldShowPlayerMetadata(): Boolean {
val prefs = PreferenceManager.getDefaultSharedPreferences(this)
return prefs.getBoolean(
getString(R.string.show_player_metadata_key),
true
)
}

fun Context.filterProviderByPreferredMedia(hasHomePageIsRequired: Boolean = true): List<MainAPI> {
// We are getting the weirdest crash ever done:
// java.lang.ClassCastException: com.lagradost.cloudstream3.TvType cannot be cast to com.lagradost.cloudstream3.TvType
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
<shape xmlns:android="http://schemas.android.com/apk/res/android">
<gradient
android:startColor="#E6000000"
android:centerColor="#99000000"
android:endColor="#00000000"
android:angle="0" />
</shape>
36 changes: 36 additions & 0 deletions app/src/main/res/drawable/metadata_overlay_icon.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24"
android:viewportHeight="24">

<path
android:fillColor="?android:attr/colorControlNormal"
android:fillAlpha="0.4"
android:pathData="
M4,6
h12
a2,2 0 0,1 2,2
v8
a2,2 0 0,1 -2,2
h-12
a2,2 0 0,1 -2,-2
v-8
a2,2 0 0,1 2,-2
z" />

<path
android:fillColor="?android:attr/colorControlNormal"
android:pathData="
M7,4
h12
a2,2 0 0,1 2,2
v8
a2,2 0 0,1 -2,2
h-12
a2,2 0 0,1 -2,-2
v-8
a2,2 0 0,1 2,-2
z" />

</vector>
80 changes: 80 additions & 0 deletions app/src/main/res/layout/player_custom_layout.xml
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,86 @@
android:orientation="vertical"
tools:orientation="vertical">

<FrameLayout
android:id="@+id/player_metadata_scrim"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:visibility="gone"
android:background="@drawable/bg_player_metadata_scrim_netflix"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintBottom_toBottomOf="parent">

<LinearLayout
android:id="@+id/player_metadata_overlay"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_gravity="center_vertical"
android:maxWidth="520dp"
android:orientation="vertical"
android:paddingStart="24dp"
android:paddingEnd="24dp"
android:paddingTop="24dp"
android:paddingBottom="24dp">

<!-- Title / Logo container -->
<FrameLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:minHeight="56dp">

<ImageView
android:id="@+id/player_movie_logo"
android:layout_width="wrap_content"
android:layout_height="48dp"
android:adjustViewBounds="true"
android:scaleType="fitStart"
android:visibility="gone"
tools:visibility="visible" />

<TextView
android:id="@+id/player_movie_title"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_gravity="center_vertical"
android:textColor="@android:color/white"
android:textSize="26sp"
android:textStyle="bold"
android:maxLines="2"
android:ellipsize="end"
tools:text="Zootopia 2" />
</FrameLayout>

<!-- Metadata -->
<TextView
android:id="@+id/player_movie_meta"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="8dp"
android:textColor="#D9FFFFFF"
android:textSize="14sp"
android:maxLines="3"
android:paddingBottom="16dp"
android:ellipsize="end"
tools:text="Animation • Action • Adventure • 2025 • ⭐ 7.6" />

<!-- Overview -->
<TextView
android:id="@+id/player_movie_overview"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="12dp"
android:textColor="#E6FFFFFF"
android:textSize="13sp"
android:lineSpacingExtra="4dp"
android:maxLines="3"
android:ellipsize="end"
tools:text="Brave rabbit cop Judy Hopps and her friend, the fox Nick Wilde, team up again to crack a new case." />

</LinearLayout>

</FrameLayout>

<ImageView
android:visibility="gone"
android:id="@+id/video_outline"
Expand Down
Loading