Added trailers

This commit is contained in:
LagradOst 2022-06-16 03:04:24 +02:00
parent 573b3488a3
commit 91a14dac0f
15 changed files with 782 additions and 107 deletions

View file

@ -174,4 +174,7 @@ dependencies {
// used for subtitle decoding https://github.com/albfernandez/juniversalchardet
implementation 'com.github.albfernandez:juniversalchardet:2.4.0'
// play yt
implementation 'com.github.HaarigerHarald:android-youtubeExtractor:master-SNAPSHOT'
}

View file

@ -901,6 +901,17 @@ interface LoadResponse {
}
}
fun LoadResponse.addTrailer(trailerUrls: List<String>?) {
if(trailerUrls == null) return
if (this.trailers == null) {
this.trailers = trailerUrls
} else {
val update = this.trailers?.toMutableList()
update?.addAll(trailerUrls)
this.trailers = update
}
}
fun LoadResponse.addImdbId(id: String?) {
// TODO add imdb sync
}

View file

@ -356,6 +356,40 @@ class MainActivity : AppCompatActivity(), ColorPickerDialogListener {
}
fun test() {
//val youtubeLink = "https://www.youtube.com/watch?v=TxB48MEAmZw"
/*
runBlocking {
val query = """
query {
searchShows(search: "spider", limit: 10) {
id
name
originalName
}
}
"""
val data =
mapOf(
"query" to query,
//"variables" to
// mapOf(
// "name" to name,
// ).toJson()
)
val txt = app.post(
"http://api.anime-skip.com/graphql",
headers = mapOf(
"X-Client-ID" to "",
"Content-Type" to "application/json",
"Accept" to "application/json",
),
json = data
)
println("TEXT: $txt")
}*/
/*runBlocking {
//https://test.api.anime-skip.com/graphiql
val txt = app.get(

View file

@ -4,10 +4,12 @@ import com.fasterxml.jackson.annotation.JsonProperty
import com.lagradost.cloudstream3.*
import com.lagradost.cloudstream3.LoadResponse.Companion.addActors
import com.lagradost.cloudstream3.LoadResponse.Companion.addImdbId
import com.lagradost.cloudstream3.LoadResponse.Companion.addTrailer
import com.lagradost.cloudstream3.utils.AppUtils.toJson
import com.uwetrottmann.tmdb2.Tmdb
import com.uwetrottmann.tmdb2.entities.*
import com.uwetrottmann.tmdb2.enumerations.AppendToResponseItem
import com.uwetrottmann.tmdb2.enumerations.VideoType
import retrofit2.awaitResponse
import java.util.*
@ -144,6 +146,7 @@ open class TmdbProvider : MainAPI() {
tags = genres?.mapNotNull { it.name }
duration = episode_run_time?.average()?.toInt()
rating = this@toLoadResponse.rating
addTrailer(videos.toTrailers())
recommendations = (this@toLoadResponse.recommendations
?: this@toLoadResponse.similar)?.results?.map { it.toSearchResponse() }
@ -151,6 +154,19 @@ open class TmdbProvider : MainAPI() {
}
}
private fun Videos?.toTrailers(): List<String>? {
return this?.results?.filter { it.type != VideoType.OPENING_CREDITS && it.type != VideoType.FEATURETTE }
?.sortedBy { it.type?.ordinal ?: 10000 }
?.mapNotNull {
when (it.site?.trim()?.lowercase()) {
"youtube" -> { // TODO FILL SITES
"https://www.youtube.com/watch?v=${it.key}"
}
else -> null
}
}
}
private fun Movie.toLoadResponse(): MovieLoadResponse {
return newMovieLoadResponse(
this.title ?: this.original_title, getUrl(id, false), TvType.Movie, TmdbLink(
@ -172,6 +188,7 @@ open class TmdbProvider : MainAPI() {
tags = genres?.mapNotNull { it.name }
duration = runtime
rating = this@toLoadResponse.rating
addTrailer(videos.toTrailers())
recommendations = (this@toLoadResponse.recommendations
?: this@toLoadResponse.similar)?.results?.map { it.toSearchResponse() }
@ -261,7 +278,16 @@ open class TmdbProvider : MainAPI() {
return if (useMetaLoadResponse) {
return if (isTvSeries) {
val body = tmdb.tvService().tv(id, "en-US", AppendToResponse(AppendToResponseItem.EXTERNAL_IDS)).awaitResponse().body()
val body = tmdb.tvService()
.tv(
id,
"en-US",
AppendToResponse(
AppendToResponseItem.EXTERNAL_IDS,
AppendToResponseItem.VIDEOS
)
)
.awaitResponse().body()
val response = body?.toLoadResponse()
if (response != null) {
if (response.recommendations.isNullOrEmpty())
@ -280,7 +306,16 @@ open class TmdbProvider : MainAPI() {
response
} else {
val body = tmdb.moviesService().summary(id, "en-US", AppendToResponse(AppendToResponseItem.EXTERNAL_IDS)).awaitResponse().body()
val body = tmdb.moviesService()
.summary(
id,
"en-US",
AppendToResponse(
AppendToResponseItem.EXTERNAL_IDS,
AppendToResponseItem.VIDEOS
)
)
.awaitResponse().body()
val response = body?.toLoadResponse()
if (response != null) {
if (response.recommendations.isNullOrEmpty())

View file

@ -93,7 +93,7 @@ class OpenSubtitlesApi(index: Int) : InAppAuthAPIManager(index), AbstractSubProv
}
private suspend fun initLogin(username: String, password: String): Boolean {
Log.i(TAG, "DATA = [$username] [$password]")
//Log.i(TAG, "DATA = [$username] [$password]")
val response = app.post(
url = "$host/login",
headers = mapOf(
@ -105,8 +105,8 @@ class OpenSubtitlesApi(index: Int) : InAppAuthAPIManager(index), AbstractSubProv
"password" to password
)
)
Log.i(TAG, "Responsecode = ${response.code}")
Log.i(TAG, "Result => ${response.text}")
//Log.i(TAG, "Responsecode = ${response.code}")
//Log.i(TAG, "Result => ${response.text}")
if (response.isSuccessful) {
AppUtils.tryParseJson<OAuthToken>(response.text)?.let { token ->

View file

@ -68,6 +68,8 @@ abstract class AbstractPlayerFragment(
var subStyle: SaveCaptionStyle? = null
var subView: SubtitleView? = null
var isBuffering = true
protected open var hasPipModeSupport = true
@LayoutRes
protected var layout: Int = R.layout.fragment_player
@ -154,7 +156,7 @@ abstract class AbstractPlayerFragment(
}
}
canEnterPipMode = isPlayingRightNow
canEnterPipMode = isPlayingRightNow && hasPipModeSupport
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O && isInPIPMode) {
activity?.let { act ->
PlayerPipHelper.updatePIPModeActions(act, isPlayingRightNow)
@ -213,7 +215,13 @@ abstract class AbstractPlayerFragment(
throw NotImplementedError()
}
private fun playerError(exception: Exception) {
private fun requestAudioFocus() {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
activity?.requestLocalAudioFocus(AppUtils.getFocusRequest())
}
}
open fun playerError(exception: Exception) {
val ctx = context ?: return
when (exception) {
is PlaybackException -> {
@ -267,12 +275,6 @@ abstract class AbstractPlayerFragment(
}
}
private fun requestAudioFocus() {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
activity?.requestLocalAudioFocus(AppUtils.getFocusRequest())
}
}
private fun onSubStyleChanged(style: SaveCaptionStyle) {
if (player is CS3IPlayer) {
player.updateSubtitleStyle(style)

View file

@ -1,11 +1,17 @@
package com.lagradost.cloudstream3.ui.player
import android.annotation.SuppressLint
import android.content.Context
import android.net.Uri
import android.os.Handler
import android.os.Looper
import android.util.Log
import android.util.SparseArray
import android.widget.FrameLayout
import androidx.core.util.forEach
import at.huber.youtubeExtractor.VideoMeta
import at.huber.youtubeExtractor.YouTubeExtractor
import at.huber.youtubeExtractor.YtFile
import com.google.android.exoplayer2.*
import com.google.android.exoplayer2.database.StandaloneDatabaseProvider
import com.google.android.exoplayer2.ext.okhttp.OkHttpDataSource
@ -23,6 +29,7 @@ import com.google.android.exoplayer2.upstream.cache.CacheDataSource
import com.google.android.exoplayer2.upstream.cache.LeastRecentlyUsedCacheEvictor
import com.google.android.exoplayer2.upstream.cache.SimpleCache
import com.google.android.exoplayer2.util.MimeTypes
import com.google.android.exoplayer2.video.VideoSize
import com.lagradost.cloudstream3.APIHolder.getApiFromName
import com.lagradost.cloudstream3.USER_AGENT
import com.lagradost.cloudstream3.app
@ -31,6 +38,7 @@ import com.lagradost.cloudstream3.mvvm.normalSafeApiCall
import com.lagradost.cloudstream3.ui.subtitles.SaveCaptionStyle
import com.lagradost.cloudstream3.utils.ExtractorLink
import com.lagradost.cloudstream3.utils.ExtractorUri
import com.lagradost.cloudstream3.utils.Qualities
import com.lagradost.cloudstream3.utils.SubtitleHelper.fromTwoLettersToLanguage
import java.io.File
import javax.net.ssl.HttpsURLConnection
@ -153,7 +161,8 @@ class CS3IPlayer : IPlayer {
data: ExtractorUri?,
startPosition: Long?,
subtitles: Set<SubtitleData>,
subtitle: SubtitleData?
subtitle: SubtitleData?,
autoPlay: Boolean?
) {
Log.i(TAG, "loadPlayer")
if (sameEpisode) {
@ -168,7 +177,7 @@ class CS3IPlayer : IPlayer {
}
// we want autoplay because of TV and UX
isPlaying = true
isPlaying = autoPlay ?: isPlaying
// release the current exoplayer and cache
releasePlayer()
@ -322,6 +331,7 @@ class CS3IPlayer : IPlayer {
}
companion object {
private var ytVideos: MutableMap<String, YtFile> = mutableMapOf()
private var simpleCache: SimpleCache? = null
var requestSubtitleUpdate: (() -> Unit)? = null
@ -686,6 +696,14 @@ class CS3IPlayer : IPlayer {
isPlaying = exo.isPlaying
}
when (playbackState) {
Player.STATE_READY -> {
onRenderFirst()
}
else -> {}
}
if (playWhenReady) {
when (playbackState) {
Player.STATE_READY -> {
@ -715,45 +733,41 @@ class CS3IPlayer : IPlayer {
// super.onCues(cues.map { cue -> cue.buildUpon().setText("Hello world").setSize(Cue.DIMEN_UNSET).build() })
//}
override fun onIsPlayingChanged(isPlaying: Boolean) {
super.onIsPlayingChanged(isPlaying)
if (isPlaying) {
onRenderFirst()
}
}
override fun onPlaybackStateChanged(playbackState: Int) {
super.onPlaybackStateChanged(playbackState)
when (playbackState) {
Player.STATE_READY -> {
requestAutoFocus?.invoke()
}
Player.STATE_ENDED -> {
handleEvent(CSPlayerEvent.NextEpisode)
}
Player.STATE_BUFFERING -> {
updatedTime()
}
Player.STATE_IDLE -> {
// IDLE
}
else -> Unit
}
}
override fun onVideoSizeChanged(videoSize: VideoSize) {
super.onVideoSizeChanged(videoSize)
playerDimensionsLoaded?.invoke(Pair(videoSize.width, videoSize.height))
}
override fun onRenderedFirstFrame() {
updatedTime()
if (!hasUsedFirstRender) { // this insures that we only call this once per player load
Log.i(TAG, "Rendered first frame")
val invalid = exoPlayer?.duration?.let { duration ->
// Only errors short playback when not playing downloaded files
duration < 20_000L && currentDownloadedFile == null
} ?: false
if (invalid) {
releasePlayer(saveTime = false)
playerError?.invoke(InvalidFileException("Too short playback"))
return
}
setPreferredSubtitles(currentSubtitles)
hasUsedFirstRender = true
val format = exoPlayer?.videoFormat
val width = format?.width
val height = format?.height
if (height != null && width != null) {
playerDimensionsLoaded?.invoke(Pair(width, height))
updatedTime()
exoPlayer?.apply {
requestedListeningPercentages?.forEach { percentage ->
createMessage { _, _ ->
updatedTime()
}
.setLooper(Looper.getMainLooper())
.setPosition( /* positionMs= */contentDuration * percentage / 100)
// .setPayload(customPayloadData)
.setDeleteAfterDelivery(false)
.send()
}
}
}
}
super.onRenderedFirstFrame()
onRenderFirst()
}
})
} catch (e: Exception) {
@ -762,6 +776,45 @@ class CS3IPlayer : IPlayer {
}
}
fun onRenderFirst() {
if (!hasUsedFirstRender) { // this insures that we only call this once per player load
Log.i(TAG, "Rendered first frame")
val invalid = exoPlayer?.duration?.let { duration ->
// Only errors short playback when not playing downloaded files
duration < 20_000L && currentDownloadedFile == null
} ?: false
if (invalid) {
releasePlayer(saveTime = false)
playerError?.invoke(InvalidFileException("Too short playback"))
return
}
setPreferredSubtitles(currentSubtitles)
hasUsedFirstRender = true
val format = exoPlayer?.videoFormat
val width = format?.width
val height = format?.height
if (height != null && width != null) {
playerDimensionsLoaded?.invoke(Pair(width, height))
updatedTime()
exoPlayer?.apply {
requestedListeningPercentages?.forEach { percentage ->
createMessage { _, _ ->
updatedTime()
}
.setLooper(Looper.getMainLooper())
.setPosition( /* positionMs= */contentDuration * percentage / 100)
// .setPayload(customPayloadData)
.setDeleteAfterDelivery(false)
.send()
}
}
}
}
}
private fun loadOfflinePlayer(context: Context, data: ExtractorUri) {
Log.i(TAG, "loadOfflinePlayer")
try {
@ -829,9 +882,55 @@ class CS3IPlayer : IPlayer {
return Pair(subSources, activeSubtitles)
}
fun loadYtFile(context: Context, yt: YtFile) {
loadOnlinePlayer(
context,
ExtractorLink(
"YouTube",
"",
yt.url,
"",
yt.format?.height ?: Qualities.Unknown.value
)
)
}
private fun loadOnlinePlayer(context: Context, link: ExtractorLink) {
Log.i(TAG, "loadOnlinePlayer")
Log.i(TAG, "loadOnlinePlayer $link")
try {
if (link.url.contains("youtube.com")) {
val ytLink = link.url.replace("/embed/", "/watch?v=")
ytVideos[ytLink]?.let {
loadYtFile(context, it)
return
}
val ytExtractor =
@SuppressLint("StaticFieldLeak")
object : YouTubeExtractor(context) {
override fun onExtractionComplete(
ytFiles: SparseArray<YtFile>?,
videoMeta: VideoMeta?
) {
var yt: YtFile? = null
ytFiles?.forEach { _, value ->
if ((yt?.format?.height ?: 0) < (value.format?.height
?: -1) && (value.format?.audioBitrate ?: -1) > 0
) {
yt = value
}
}
yt?.let { ytf ->
ytVideos[ytLink] = ytf
loadYtFile(context, ytf)
}
}
}
Log.i(TAG, "YouTube extraction on $ytLink")
ytExtractor.extract(ytLink)
return
}
currentLink = link
if (ignoreSSL) {

View file

@ -51,6 +51,21 @@ import com.lagradost.cloudstream3.utils.UIHelper.showSystemUI
import com.lagradost.cloudstream3.utils.UIHelper.toPx
import com.lagradost.cloudstream3.utils.Vector2
import kotlinx.android.synthetic.main.player_custom_layout.*
import kotlinx.android.synthetic.main.player_custom_layout.bottom_player_bar
import kotlinx.android.synthetic.main.player_custom_layout.exo_ffwd
import kotlinx.android.synthetic.main.player_custom_layout.exo_ffwd_text
import kotlinx.android.synthetic.main.player_custom_layout.exo_progress
import kotlinx.android.synthetic.main.player_custom_layout.exo_rew
import kotlinx.android.synthetic.main.player_custom_layout.exo_rew_text
import kotlinx.android.synthetic.main.player_custom_layout.player_center_menu
import kotlinx.android.synthetic.main.player_custom_layout.player_ffwd_holder
import kotlinx.android.synthetic.main.player_custom_layout.player_holder
import kotlinx.android.synthetic.main.player_custom_layout.player_pause_play
import kotlinx.android.synthetic.main.player_custom_layout.player_pause_play_holder
import kotlinx.android.synthetic.main.player_custom_layout.player_rew_holder
import kotlinx.android.synthetic.main.player_custom_layout.player_video_bar
import kotlinx.android.synthetic.main.player_custom_layout.shadow_overlay
import kotlinx.android.synthetic.main.trailer_custom_layout.*
import kotlin.math.*
const val MINIMUM_SEEK_TIME = 7000L // when swipe seeking
@ -64,6 +79,9 @@ const val DOUBLE_TAB_PAUSE_PERCENTAGE = 0.15 // in both directions
// All the UI Logic for the player
open class FullScreenPlayer : AbstractPlayerFragment() {
protected open var lockRotation = true
protected open var isFullScreenPlayer = true
// state of player UI
protected var isShowing = false
protected var isLocked = false
@ -100,11 +118,11 @@ open class FullScreenPlayer : AbstractPlayerFragment() {
// screenWidth and screenHeight does always
// refer to the screen while in landscape mode
private val screenWidth: Int
protected val screenWidth: Int
get() {
return max(displayMetrics.widthPixels, displayMetrics.heightPixels)
}
private val screenHeight: Int
protected val screenHeight: Int
get() {
return min(displayMetrics.widthPixels, displayMetrics.heightPixels)
}
@ -159,7 +177,7 @@ open class FullScreenPlayer : AbstractPlayerFragment() {
animateLayoutChanges()
}
private fun animateLayoutChanges() {
protected fun animateLayoutChanges() {
if (isShowing) {
updateUIVisibility()
} else {
@ -234,20 +252,25 @@ open class FullScreenPlayer : AbstractPlayerFragment() {
}
override fun onResume() {
activity?.hideSystemUI()
activity?.requestedOrientation = ActivityInfo.SCREEN_ORIENTATION_SENSOR_LANDSCAPE
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P && fullscreenNotch) {
val params = activity?.window?.attributes
params?.layoutInDisplayCutoutMode = LAYOUT_IN_DISPLAY_CUTOUT_MODE_SHORT_EDGES
activity?.window?.attributes = params
if (isFullScreenPlayer) {
activity?.hideSystemUI()
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P && fullscreenNotch) {
val params = activity?.window?.attributes
params?.layoutInDisplayCutoutMode = LAYOUT_IN_DISPLAY_CUTOUT_MODE_SHORT_EDGES
activity?.window?.attributes = params
}
}
if (lockRotation)
activity?.requestedOrientation = ActivityInfo.SCREEN_ORIENTATION_SENSOR_LANDSCAPE
super.onResume()
}
override fun onDestroy() {
activity?.showSystemUI()
activity?.requestedOrientation = ActivityInfo.SCREEN_ORIENTATION_USER
if (lockRotation)
activity?.requestedOrientation = ActivityInfo.SCREEN_ORIENTATION_USER
// simply resets brightness and notch settings that might have been overridden
val lp = activity?.window?.attributes
@ -336,7 +359,8 @@ open class FullScreenPlayer : AbstractPlayerFragment() {
}
dialog.setOnDismissListener {
activity?.hideSystemUI()
if (isFullScreenPlayer)
activity?.hideSystemUI()
}
applyButton.setOnClickListener {
dialog.dismissSafe(activity)
@ -374,9 +398,11 @@ open class FullScreenPlayer : AbstractPlayerFragment() {
act.getString(R.string.player_speed),
false,
{
activity?.hideSystemUI()
if (isFullScreenPlayer)
activity?.hideSystemUI()
}) { index ->
activity?.hideSystemUI()
if (isFullScreenPlayer)
activity?.hideSystemUI()
setPlayBackSpeed(speedsNumbers[index])
}
}
@ -455,9 +481,11 @@ open class FullScreenPlayer : AbstractPlayerFragment() {
private fun onClickChange() {
isShowing = !isShowing
if (isShowing) {
player_intro_play?.isGone = true
autoHide()
}
activity?.hideSystemUI()
if (isFullScreenPlayer)
activity?.hideSystemUI()
animateLayoutChanges()
player_pause_play?.requestFocus()
}
@ -692,7 +720,7 @@ open class FullScreenPlayer : AbstractPlayerFragment() {
if (event == null || view == null) return false
val currentTouch = Vector2(event.x, event.y)
val startTouch = currentTouchStart
player_intro_play?.isGone = true
when (event.action) {
MotionEvent.ACTION_DOWN -> {
// validates if the touch is inside of the player area
@ -717,7 +745,7 @@ open class FullScreenPlayer : AbstractPlayerFragment() {
}
}
MotionEvent.ACTION_UP -> {
if (isCurrentTouchValid && !isLocked) {
if (isCurrentTouchValid && !isLocked && isFullScreenPlayer) {
// seek time
if (swipeHorizontalEnabled && currentTouchAction == TouchAction.Time) {
val startTime = currentTouchStartPlayerTime
@ -749,18 +777,18 @@ open class FullScreenPlayer : AbstractPlayerFragment() {
if (doubleTapPauseEnabled) { // you can pause if your tap is in the middle of the screen
when {
currentTouch.x < screenWidth / 2 - (DOUBLE_TAB_PAUSE_PERCENTAGE * screenWidth) -> {
if (doubleTapEnabled)
if (doubleTapEnabled && isFullScreenPlayer)
rewind()
}
currentTouch.x > screenWidth / 2 + (DOUBLE_TAB_PAUSE_PERCENTAGE * screenWidth) -> {
if (doubleTapEnabled)
if (doubleTapEnabled && isFullScreenPlayer)
fastForward()
}
else -> {
player.handleEvent(CSPlayerEvent.PlayPauseToggle)
}
}
} else if (doubleTapEnabled) {
} else if (doubleTapEnabled && isFullScreenPlayer) {
if (currentTouch.x < screenWidth / 2) {
rewind()
} else {
@ -798,7 +826,7 @@ open class FullScreenPlayer : AbstractPlayerFragment() {
}
MotionEvent.ACTION_MOVE -> {
// if current touch is valid
if (startTouch != null && isCurrentTouchValid && !isLocked) {
if (startTouch != null && isCurrentTouchValid && !isLocked && isFullScreenPlayer) {
// action is unassigned and can therefore be assigned
if (currentTouchAction == null) {
val diffFromStart = startTouch - currentTouch
@ -1201,6 +1229,11 @@ open class FullScreenPlayer : AbstractPlayerFragment() {
showMirrorsDialogue()
}
player_intro_play?.setOnClickListener {
player_intro_play?.isGone = true
player.handleEvent(CSPlayerEvent.Play)
}
// it is !not! a bug that you cant touch the right side, it does not register inputs on navbar or status bar
player_holder?.setOnTouchListener { callView, event ->
return@setOnTouchListener handleMotionEvent(callView, event)

View file

@ -98,6 +98,7 @@ interface IPlayer {
startPosition: Long? = null,
subtitles : Set<SubtitleData>,
subtitle : SubtitleData?,
autoPlay : Boolean? = true
)
fun reloadPlayer(context: Context)

View file

@ -28,7 +28,6 @@ import androidx.core.view.isGone
import androidx.core.view.isVisible
import androidx.core.widget.NestedScrollView
import androidx.core.widget.doOnTextChanged
import androidx.fragment.app.Fragment
import androidx.lifecycle.ViewModelProvider
import androidx.preference.PreferenceManager
import androidx.recyclerview.widget.GridLayoutManager
@ -85,7 +84,6 @@ import com.lagradost.cloudstream3.utils.UIHelper.popupMenuNoIcons
import com.lagradost.cloudstream3.utils.UIHelper.popupMenuNoIconsAndNoStringRes
import com.lagradost.cloudstream3.utils.UIHelper.requestRW
import com.lagradost.cloudstream3.utils.UIHelper.setImage
import com.lagradost.cloudstream3.utils.UIHelper.setImageBlur
import com.lagradost.cloudstream3.utils.VideoDownloadManager.getFileName
import com.lagradost.cloudstream3.utils.VideoDownloadManager.sanitizeFilename
import kotlinx.android.synthetic.main.fragment_result.*
@ -183,7 +181,7 @@ fun ResultEpisode.getWatchProgress(): Float {
return (getDisplayPosition() / 1000).toFloat() / (duration / 1000).toFloat()
}
class ResultFragment : Fragment(), PanelsChildGestureRegionObserver.GestureRegionsListener {
class ResultFragment : ResultTrailerPlayer() {
companion object {
const val URL_BUNDLE = "url"
const val API_NAME_BUNDLE = "apiName"
@ -601,6 +599,53 @@ class ResultFragment : Fragment(), PanelsChildGestureRegionObserver.GestureRegio
setFormatText(result_meta_rating, R.string.rating_format, rating?.div(1000f))
}
var currentTrailers: List<String> = emptyList()
var currentTrailerIndex = 0
override fun nextMirror() {
currentTrailerIndex++
loadTrailer()
}
override fun playerError(exception: Exception) {
if (player.getIsPlaying()) // because we dont want random toasts in player
super.playerError(exception)
}
private fun loadTrailer(index: Int? = null) {
currentTrailers.getOrNull(index ?: currentTrailerIndex)?.let { trailer ->
//if(trailer.contains("youtube.com")) { // wont load in exo
// nextMirror()
// return
//}
context?.let { ctx ->
player.onPause()
player.loadPlayer(
ctx,
false,
ExtractorLink(
"",
"Trailer",
trailer,
"",
Qualities.Unknown.value
),
null,
startPosition = 0L,
subtitles = emptySet(),
subtitle = null,
autoPlay = false
)
}
}
}
private fun setTrailers(trailers: List<String>?) {
if(context?.isTvSettings() == true) return
currentTrailers = trailers ?: emptyList()
loadTrailer()
}
private fun setActors(actors: List<ActorData>?) {
if (actors.isNullOrEmpty()) {
result_cast_text?.isVisible = false
@ -777,7 +822,7 @@ class ResultFragment : Fragment(), PanelsChildGestureRegionObserver.GestureRegio
} else if (dy < -5) {
result_bookmark_fab?.extend()
}
result_poster_blur_holder?.translationY = -scrollY.toFloat()
//result_poster_blur_holder?.translationY = -scrollY.toFloat()
})
result_back.setOnClickListener {
@ -1727,6 +1772,8 @@ class ResultFragment : Fragment(), PanelsChildGestureRegionObserver.GestureRegio
setRecommendations(d.recommendations, null)
setActors(d.actors)
setTrailers(d.trailers)
if (syncModel.addSyncs(d.syncData)) {
syncModel.updateMetaAndUser()
syncModel.updateSynced()
@ -1739,7 +1786,7 @@ class ResultFragment : Fragment(), PanelsChildGestureRegionObserver.GestureRegio
val posterImageLink = d.posterUrl
if (!posterImageLink.isNullOrEmpty()) {
result_poster?.setImage(posterImageLink, d.posterHeaders)
result_poster_blur?.setImageBlur(posterImageLink, 10, 3, d.posterHeaders)
//result_poster_blur?.setImageBlur(posterImageLink, 10, 3, d.posterHeaders)
//Full screen view of Poster image
if (context?.isTrueTvSettings() == false) // Poster not clickable on tv
result_poster_holder?.setOnClickListener {
@ -1768,7 +1815,7 @@ class ResultFragment : Fragment(), PanelsChildGestureRegionObserver.GestureRegio
} else {
result_poster?.setImageResource(R.drawable.default_cover)
result_poster_blur?.setImageResource(R.drawable.default_cover)
//result_poster_blur?.setImageResource(R.drawable.default_cover)
}
result_poster_holder?.visibility = VISIBLE

View file

@ -0,0 +1,68 @@
package com.lagradost.cloudstream3.ui.result
import android.content.res.Configuration
import android.graphics.Rect
import android.widget.LinearLayout
import androidx.core.view.isVisible
import com.discord.panels.PanelsChildGestureRegionObserver
import com.lagradost.cloudstream3.ui.player.SubtitleData
import kotlinx.android.synthetic.main.fragment_trailer.*
open class ResultTrailerPlayer : com.lagradost.cloudstream3.ui.player.FullScreenPlayer(),
PanelsChildGestureRegionObserver.GestureRegionsListener {
override var lockRotation = false
override var isFullScreenPlayer = false
override var hasPipModeSupport = false
companion object {
const val TAG = "RESULT_TRAILER"
}
var playerWidthHeight: Pair<Int, Int>? = null
override fun nextEpisode() {}
override fun prevEpisode() {}
override fun playerPositionChanged(posDur: Pair<Long, Long>) {}
override fun nextMirror() {}
override fun onConfigurationChanged(newConfig: Configuration) {
super.onConfigurationChanged(newConfig)
uiReset()
fixPlayerSize()
}
private fun fixPlayerSize() {
playerWidthHeight?.let { (w, h) ->
val orientation = this.resources.configuration?.orientation ?: return
val sw = if (orientation == Configuration.ORIENTATION_LANDSCAPE) {
screenWidth
} else {
screenHeight
}
player_background?.apply {
isVisible = true
layoutParams =
LinearLayout.LayoutParams(LinearLayout.LayoutParams.MATCH_PARENT, sw * h / w)
}
}
}
override fun playerDimensionsLoaded(widthHeight: Pair<Int, Int>) {
playerWidthHeight = widthHeight
fixPlayerSize()
}
override fun subtitlesChanged() {}
override fun embeddedSubtitlesFetched(subtitles: List<SubtitleData>) {}
override fun exitedPipMode() {}
override fun onGestureRegionsUpdate(gestureRegions: List<Rect>) {}
}

View file

@ -117,38 +117,13 @@
android:textColor="?attr/textColor" />
</LinearLayout>
<FrameLayout
<LinearLayout
android:id="@+id/result_finish_loading"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:visibility="gone"
tools:visibility="visible">
<FrameLayout
android:id="@+id/result_poster_blur_holder"
android:layout_width="match_parent"
android:layout_height="230dp"
android:visibility="visible">
<ImageView
android:id="@+id/result_poster_blur"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:alpha="0"
android:background="?attr/primaryGrayBackground"
android:scaleType="centerCrop"
tools:ignore="ContentDescription"
tools:src="@drawable/example_poster" />
<ImageView
android:layout_width="match_parent"
android:layout_height="30dp"
android:layout_gravity="bottom"
android:src="@drawable/background_shadow"
tools:ignore="ContentDescription" />
</FrameLayout>
tools:visibility="visible"
android:orientation="vertical">
<androidx.core.widget.NestedScrollView
android:id="@+id/result_scroll"
@ -163,6 +138,8 @@
android:background="?attr/primaryBlackBackground"
android:orientation="vertical">
<include layout="@layout/fragment_trailer" />
<!--
<FrameLayout
android:background="?attr/primaryGrayBackground"
@ -399,6 +376,7 @@
tools:text="Bookmark"
tools:visibility="visible" />
<TextView
android:id="@+id/result_cast_text"
android:layout_width="wrap_content"
@ -855,7 +833,7 @@
</LinearLayout>
</androidx.core.widget.NestedScrollView>
</FrameLayout>
</LinearLayout>
<!--

View file

@ -0,0 +1,33 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="0dp"
android:visibility="visible"
android:orientation="horizontal"
android:keepScreenOn="true"
android:id="@+id/player_background"
app:backgroundTint="@android:color/black"
android:background="@android:color/black"
android:screenOrientation="sensorLandscape"
app:surface_type="texture_view">
<!--
app:fastforward_increment="10000"
app:rewind_increment="10000"-->
<com.google.android.exoplayer2.ui.PlayerView
android:id="@+id/player_view"
app:show_timeout="0"
app:hide_on_touch="false"
app:auto_show="true"
android:layout_width="match_parent"
android:layout_height="match_parent"
app:backgroundTint="@android:color/black"
android:background="@android:color/black"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:controller_layout_id="@layout/trailer_custom_layout" />
</androidx.constraintlayout.widget.ConstraintLayout>

View file

@ -0,0 +1,330 @@
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/player_holder"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
android:screenOrientation="landscape"
tools:orientation="vertical">
<FrameLayout
android:id="@+id/player_intro_play"
android:layout_width="match_parent"
android:layout_height="match_parent">
<View
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@drawable/player_gradient_tv" />
<TextView
android:textSize="20sp"
android:textStyle="bold"
android:textColor="@android:color/white"
android:text="@string/trailer"
android:padding="10dp"
android:layout_gravity="start|bottom"
android:layout_width="wrap_content"
android:layout_height="wrap_content" />
<ImageView
android:layout_gravity="center"
android:src="@drawable/play_button"
android:layout_width="60dp"
android:layout_height="60dp" />
</FrameLayout>
<FrameLayout
android:id="@+id/subtitle_holder"
android:layout_width="match_parent"
android:layout_height="match_parent">
<View
android:id="@+id/shadow_overlay"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@color/black_overlay" />
</FrameLayout>
<androidx.constraintlayout.widget.ConstraintLayout
android:id="@+id/player_video_holder"
android:layout_width="match_parent"
android:layout_height="match_parent">
<!--use for thinner app:trackThickness="3dp" com.google.android.material.progressindicator.CircularProgressIndicator-->
<ProgressBar
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toTopOf="parent"
android:focusable="false"
android:clickable="false"
android:focusableInTouchMode="false"
android:indeterminate="true"
android:visibility="gone"
tools:visibility="visible"
android:id="@+id/player_buffering"
android:layout_gravity="center"
android:layout_height="wrap_content"
android:layout_width="wrap_content" />
<!-- This nested layout is necessary because of buffering and clicking-->
<FrameLayout
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toTopOf="parent"
android:layout_width="100dp"
android:layout_height="100dp"
android:id="@+id/player_pause_play_holder_holder">
<FrameLayout
tools:ignore="uselessParent"
android:id="@+id/player_pause_play_holder"
android:layout_width="match_parent"
android:layout_height="match_parent">
<ImageView
app:tint="@color/white"
android:id="@+id/player_pause_play"
android:nextFocusLeft="@id/exo_rew"
android:nextFocusRight="@id/exo_ffwd"
android:nextFocusUp="@id/player_go_back"
android:nextFocusDown="@id/player_lock"
android:layout_gravity="center"
android:src="@drawable/netflix_pause"
android:background="@drawable/video_tap_button"
android:layout_width="70dp"
android:layout_height="70dp"
tools:ignore="ContentDescription" />
</FrameLayout>
</FrameLayout>
<androidx.constraintlayout.widget.ConstraintLayout
android:id="@+id/player_center_menu"
android:layout_width="match_parent"
android:layout_height="100dp"
android:layout_gravity="center"
android:gravity="center"
android:orientation="horizontal"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toTopOf="parent">
<FrameLayout
android:id="@+id/player_rew_holder"
android:layout_width="0dp"
android:layout_height="wrap_content"
app:layout_constraintWidth_percent="0.5"
android:layout_gravity="center_vertical|start"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toLeftOf="@id/player_ffwd_holder"
app:layout_constraintTop_toTopOf="parent">
<TextView
android:id="@+id/exo_rew_text"
android:layout_width="200dp"
android:layout_height="40dp"
android:layout_gravity="center"
android:gravity="center"
android:textColor="@color/white"
android:textSize="19sp"
android:textStyle="bold"
tools:text="10" />
<ImageButton
android:id="@id/exo_rew"
android:layout_width="70dp"
android:layout_height="70dp"
android:layout_gravity="center"
android:background="@drawable/video_tap_button_skip"
android:nextFocusLeft="@id/exo_rew"
android:nextFocusUp="@id/player_go_back"
android:nextFocusDown="@id/player_lock"
android:padding="10dp"
android:scaleType="fitCenter"
android:scaleX="-1"
android:src="@drawable/netflix_skip_forward"
app:tint="@color/white"
android:tintMode="src_in"
tools:ignore="ContentDescription" />
</FrameLayout>
<FrameLayout
android:id="@+id/player_ffwd_holder"
android:layout_width="0dp"
app:layout_constraintWidth_percent="0.5"
android:layout_height="wrap_content"
android:layout_gravity="center_vertical|end"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintLeft_toRightOf="@id/player_rew_holder"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toTopOf="parent">
<TextView
android:id="@+id/exo_ffwd_text"
android:layout_width="200dp"
android:layout_height="40dp"
android:layout_gravity="center"
android:gravity="center"
android:textColor="@color/white"
android:textSize="19sp"
android:textStyle="bold"
tools:text="10" />
<ImageButton
android:id="@id/exo_ffwd"
android:layout_width="70dp"
android:layout_height="70dp"
android:layout_gravity="center"
android:background="@drawable/video_tap_button_skip"
android:nextFocusRight="@id/exo_rew"
android:nextFocusUp="@id/player_go_back"
android:nextFocusDown="@id/player_lock"
android:padding="10dp"
android:scaleType="fitCenter"
android:src="@drawable/netflix_skip_forward"
app:tint="@color/white"
android:tintMode="src_in"
tools:ignore="ContentDescription" />
</FrameLayout>
</androidx.constraintlayout.widget.ConstraintLayout>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_gravity="bottom"
android:layout_marginBottom="20dp"
android:gravity="center"
android:orientation="horizontal"
android:paddingTop="4dp"
android:visibility="gone"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent">
<ImageButton
android:id="@id/exo_prev"
style="@style/ExoMediaButton.Previous"
app:tint="?attr/colorPrimaryDark"
android:tintMode="src_in"
tools:ignore="ContentDescription" />
<ImageButton
android:id="@id/exo_repeat_toggle"
style="@style/ExoMediaButton"
app:tint="?attr/colorPrimaryDark"
android:tintMode="src_in"
tools:ignore="ContentDescription" />
<ImageButton
android:id="@id/exo_next"
style="@style/ExoMediaButton.Next"
app:tint="?attr/colorPrimaryDark"
android:tintMode="src_in"
tools:ignore="ContentDescription" />
<ImageButton
android:id="@id/exo_vr"
style="@style/ExoMediaButton.VR"
app:tint="?attr/colorPrimaryDark"
android:tintMode="src_in"
tools:ignore="ContentDescription" />
<ImageButton
android:id="@id/exo_play"
app:tint="?attr/colorPrimaryDark"
android:tintMode="src_in"
tools:ignore="ContentDescription"
android:layout_height="0dp"
android:layout_width="0dp" />
<ImageButton
android:id="@id/exo_pause"
app:tint="?attr/colorPrimaryDark"
android:tintMode="src_in"
tools:ignore="ContentDescription"
android:layout_height="0dp"
android:layout_width="0dp" />
</LinearLayout>
<LinearLayout
android:id="@+id/bottom_player_bar"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginBottom="10dp"
android:gravity="center_vertical"
android:orientation="vertical"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent">
<LinearLayout
android:id="@+id/player_video_bar"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal">
<TextView
android:id="@id/exo_position"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:layout_marginStart="20dp"
android:gravity="end"
android:includeFontPadding="false"
android:minWidth="50dp"
android:paddingLeft="4dp"
android:paddingRight="4dp"
android:textColor="@android:color/white"
android:textSize="14sp"
android:textStyle="normal"
tools:text="15:30" />
<!--app:buffered_color="@color/videoCache"-->
<com.google.android.exoplayer2.ui.DefaultTimeBar
android:id="@id/exo_progress"
android:layout_width="0dp"
android:layout_height="30dp"
android:layout_weight="1"
app:bar_height="2dp"
app:played_color="?attr/colorPrimary"
app:scrubber_color="?attr/colorPrimary"
app:scrubber_dragged_size="26dp"
app:scrubber_enabled_size="24dp"
app:unplayed_color="@color/videoProgress" />
<!-- exo_duration-->
<TextView
android:id="@id/exo_duration"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:layout_marginEnd="20dp"
android:includeFontPadding="false"
android:minWidth="50dp"
android:paddingLeft="4dp"
android:paddingRight="4dp"
android:textColor="@android:color/white"
android:textSize="14sp"
android:textStyle="normal"
tools:text="23:20" />
</LinearLayout>
</LinearLayout>
</androidx.constraintlayout.widget.ConstraintLayout>
</FrameLayout>

View file

@ -530,4 +530,5 @@
<string name="subtitles_remove_captions">Remove closed captions from subtitles</string>
<string name="subtitles_remove_bloat">Remove bloat from subtitles</string>
<string name="extras">Extras</string>
<string name="trailer">Trailer</string>
</resources>