mirror of
https://github.com/recloudstream/cloudstream.git
synced 2024-08-15 01:53:11 +00:00
Added trailers
This commit is contained in:
parent
573b3488a3
commit
91a14dac0f
15 changed files with 782 additions and 107 deletions
|
@ -174,4 +174,7 @@ dependencies {
|
||||||
|
|
||||||
// used for subtitle decoding https://github.com/albfernandez/juniversalchardet
|
// used for subtitle decoding https://github.com/albfernandez/juniversalchardet
|
||||||
implementation 'com.github.albfernandez:juniversalchardet:2.4.0'
|
implementation 'com.github.albfernandez:juniversalchardet:2.4.0'
|
||||||
|
|
||||||
|
// play yt
|
||||||
|
implementation 'com.github.HaarigerHarald:android-youtubeExtractor:master-SNAPSHOT'
|
||||||
}
|
}
|
|
@ -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?) {
|
fun LoadResponse.addImdbId(id: String?) {
|
||||||
// TODO add imdb sync
|
// TODO add imdb sync
|
||||||
}
|
}
|
||||||
|
|
|
@ -356,6 +356,40 @@ class MainActivity : AppCompatActivity(), ColorPickerDialogListener {
|
||||||
}
|
}
|
||||||
|
|
||||||
fun test() {
|
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 {
|
/*runBlocking {
|
||||||
//https://test.api.anime-skip.com/graphiql
|
//https://test.api.anime-skip.com/graphiql
|
||||||
val txt = app.get(
|
val txt = app.get(
|
||||||
|
|
|
@ -4,10 +4,12 @@ import com.fasterxml.jackson.annotation.JsonProperty
|
||||||
import com.lagradost.cloudstream3.*
|
import com.lagradost.cloudstream3.*
|
||||||
import com.lagradost.cloudstream3.LoadResponse.Companion.addActors
|
import com.lagradost.cloudstream3.LoadResponse.Companion.addActors
|
||||||
import com.lagradost.cloudstream3.LoadResponse.Companion.addImdbId
|
import com.lagradost.cloudstream3.LoadResponse.Companion.addImdbId
|
||||||
|
import com.lagradost.cloudstream3.LoadResponse.Companion.addTrailer
|
||||||
import com.lagradost.cloudstream3.utils.AppUtils.toJson
|
import com.lagradost.cloudstream3.utils.AppUtils.toJson
|
||||||
import com.uwetrottmann.tmdb2.Tmdb
|
import com.uwetrottmann.tmdb2.Tmdb
|
||||||
import com.uwetrottmann.tmdb2.entities.*
|
import com.uwetrottmann.tmdb2.entities.*
|
||||||
import com.uwetrottmann.tmdb2.enumerations.AppendToResponseItem
|
import com.uwetrottmann.tmdb2.enumerations.AppendToResponseItem
|
||||||
|
import com.uwetrottmann.tmdb2.enumerations.VideoType
|
||||||
import retrofit2.awaitResponse
|
import retrofit2.awaitResponse
|
||||||
import java.util.*
|
import java.util.*
|
||||||
|
|
||||||
|
@ -144,6 +146,7 @@ open class TmdbProvider : MainAPI() {
|
||||||
tags = genres?.mapNotNull { it.name }
|
tags = genres?.mapNotNull { it.name }
|
||||||
duration = episode_run_time?.average()?.toInt()
|
duration = episode_run_time?.average()?.toInt()
|
||||||
rating = this@toLoadResponse.rating
|
rating = this@toLoadResponse.rating
|
||||||
|
addTrailer(videos.toTrailers())
|
||||||
|
|
||||||
recommendations = (this@toLoadResponse.recommendations
|
recommendations = (this@toLoadResponse.recommendations
|
||||||
?: this@toLoadResponse.similar)?.results?.map { it.toSearchResponse() }
|
?: 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 {
|
private fun Movie.toLoadResponse(): MovieLoadResponse {
|
||||||
return newMovieLoadResponse(
|
return newMovieLoadResponse(
|
||||||
this.title ?: this.original_title, getUrl(id, false), TvType.Movie, TmdbLink(
|
this.title ?: this.original_title, getUrl(id, false), TvType.Movie, TmdbLink(
|
||||||
|
@ -172,6 +188,7 @@ open class TmdbProvider : MainAPI() {
|
||||||
tags = genres?.mapNotNull { it.name }
|
tags = genres?.mapNotNull { it.name }
|
||||||
duration = runtime
|
duration = runtime
|
||||||
rating = this@toLoadResponse.rating
|
rating = this@toLoadResponse.rating
|
||||||
|
addTrailer(videos.toTrailers())
|
||||||
|
|
||||||
recommendations = (this@toLoadResponse.recommendations
|
recommendations = (this@toLoadResponse.recommendations
|
||||||
?: this@toLoadResponse.similar)?.results?.map { it.toSearchResponse() }
|
?: this@toLoadResponse.similar)?.results?.map { it.toSearchResponse() }
|
||||||
|
@ -261,7 +278,16 @@ open class TmdbProvider : MainAPI() {
|
||||||
|
|
||||||
return if (useMetaLoadResponse) {
|
return if (useMetaLoadResponse) {
|
||||||
return if (isTvSeries) {
|
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()
|
val response = body?.toLoadResponse()
|
||||||
if (response != null) {
|
if (response != null) {
|
||||||
if (response.recommendations.isNullOrEmpty())
|
if (response.recommendations.isNullOrEmpty())
|
||||||
|
@ -280,7 +306,16 @@ open class TmdbProvider : MainAPI() {
|
||||||
|
|
||||||
response
|
response
|
||||||
} else {
|
} 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()
|
val response = body?.toLoadResponse()
|
||||||
if (response != null) {
|
if (response != null) {
|
||||||
if (response.recommendations.isNullOrEmpty())
|
if (response.recommendations.isNullOrEmpty())
|
||||||
|
|
|
@ -93,7 +93,7 @@ class OpenSubtitlesApi(index: Int) : InAppAuthAPIManager(index), AbstractSubProv
|
||||||
}
|
}
|
||||||
|
|
||||||
private suspend fun initLogin(username: String, password: String): Boolean {
|
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(
|
val response = app.post(
|
||||||
url = "$host/login",
|
url = "$host/login",
|
||||||
headers = mapOf(
|
headers = mapOf(
|
||||||
|
@ -105,8 +105,8 @@ class OpenSubtitlesApi(index: Int) : InAppAuthAPIManager(index), AbstractSubProv
|
||||||
"password" to password
|
"password" to password
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
Log.i(TAG, "Responsecode = ${response.code}")
|
//Log.i(TAG, "Responsecode = ${response.code}")
|
||||||
Log.i(TAG, "Result => ${response.text}")
|
//Log.i(TAG, "Result => ${response.text}")
|
||||||
|
|
||||||
if (response.isSuccessful) {
|
if (response.isSuccessful) {
|
||||||
AppUtils.tryParseJson<OAuthToken>(response.text)?.let { token ->
|
AppUtils.tryParseJson<OAuthToken>(response.text)?.let { token ->
|
||||||
|
|
|
@ -68,6 +68,8 @@ abstract class AbstractPlayerFragment(
|
||||||
var subStyle: SaveCaptionStyle? = null
|
var subStyle: SaveCaptionStyle? = null
|
||||||
var subView: SubtitleView? = null
|
var subView: SubtitleView? = null
|
||||||
var isBuffering = true
|
var isBuffering = true
|
||||||
|
protected open var hasPipModeSupport = true
|
||||||
|
|
||||||
|
|
||||||
@LayoutRes
|
@LayoutRes
|
||||||
protected var layout: Int = R.layout.fragment_player
|
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) {
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O && isInPIPMode) {
|
||||||
activity?.let { act ->
|
activity?.let { act ->
|
||||||
PlayerPipHelper.updatePIPModeActions(act, isPlayingRightNow)
|
PlayerPipHelper.updatePIPModeActions(act, isPlayingRightNow)
|
||||||
|
@ -213,7 +215,13 @@ abstract class AbstractPlayerFragment(
|
||||||
throw NotImplementedError()
|
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
|
val ctx = context ?: return
|
||||||
when (exception) {
|
when (exception) {
|
||||||
is PlaybackException -> {
|
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) {
|
private fun onSubStyleChanged(style: SaveCaptionStyle) {
|
||||||
if (player is CS3IPlayer) {
|
if (player is CS3IPlayer) {
|
||||||
player.updateSubtitleStyle(style)
|
player.updateSubtitleStyle(style)
|
||||||
|
|
|
@ -1,11 +1,17 @@
|
||||||
package com.lagradost.cloudstream3.ui.player
|
package com.lagradost.cloudstream3.ui.player
|
||||||
|
|
||||||
|
import android.annotation.SuppressLint
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.net.Uri
|
import android.net.Uri
|
||||||
import android.os.Handler
|
import android.os.Handler
|
||||||
import android.os.Looper
|
import android.os.Looper
|
||||||
import android.util.Log
|
import android.util.Log
|
||||||
|
import android.util.SparseArray
|
||||||
import android.widget.FrameLayout
|
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.*
|
||||||
import com.google.android.exoplayer2.database.StandaloneDatabaseProvider
|
import com.google.android.exoplayer2.database.StandaloneDatabaseProvider
|
||||||
import com.google.android.exoplayer2.ext.okhttp.OkHttpDataSource
|
import com.google.android.exoplayer2.ext.okhttp.OkHttpDataSource
|
||||||
|
@ -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.LeastRecentlyUsedCacheEvictor
|
||||||
import com.google.android.exoplayer2.upstream.cache.SimpleCache
|
import com.google.android.exoplayer2.upstream.cache.SimpleCache
|
||||||
import com.google.android.exoplayer2.util.MimeTypes
|
import com.google.android.exoplayer2.util.MimeTypes
|
||||||
|
import com.google.android.exoplayer2.video.VideoSize
|
||||||
import com.lagradost.cloudstream3.APIHolder.getApiFromName
|
import com.lagradost.cloudstream3.APIHolder.getApiFromName
|
||||||
import com.lagradost.cloudstream3.USER_AGENT
|
import com.lagradost.cloudstream3.USER_AGENT
|
||||||
import com.lagradost.cloudstream3.app
|
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.ui.subtitles.SaveCaptionStyle
|
||||||
import com.lagradost.cloudstream3.utils.ExtractorLink
|
import com.lagradost.cloudstream3.utils.ExtractorLink
|
||||||
import com.lagradost.cloudstream3.utils.ExtractorUri
|
import com.lagradost.cloudstream3.utils.ExtractorUri
|
||||||
|
import com.lagradost.cloudstream3.utils.Qualities
|
||||||
import com.lagradost.cloudstream3.utils.SubtitleHelper.fromTwoLettersToLanguage
|
import com.lagradost.cloudstream3.utils.SubtitleHelper.fromTwoLettersToLanguage
|
||||||
import java.io.File
|
import java.io.File
|
||||||
import javax.net.ssl.HttpsURLConnection
|
import javax.net.ssl.HttpsURLConnection
|
||||||
|
@ -153,7 +161,8 @@ class CS3IPlayer : IPlayer {
|
||||||
data: ExtractorUri?,
|
data: ExtractorUri?,
|
||||||
startPosition: Long?,
|
startPosition: Long?,
|
||||||
subtitles: Set<SubtitleData>,
|
subtitles: Set<SubtitleData>,
|
||||||
subtitle: SubtitleData?
|
subtitle: SubtitleData?,
|
||||||
|
autoPlay: Boolean?
|
||||||
) {
|
) {
|
||||||
Log.i(TAG, "loadPlayer")
|
Log.i(TAG, "loadPlayer")
|
||||||
if (sameEpisode) {
|
if (sameEpisode) {
|
||||||
|
@ -168,7 +177,7 @@ class CS3IPlayer : IPlayer {
|
||||||
}
|
}
|
||||||
|
|
||||||
// we want autoplay because of TV and UX
|
// we want autoplay because of TV and UX
|
||||||
isPlaying = true
|
isPlaying = autoPlay ?: isPlaying
|
||||||
|
|
||||||
// release the current exoplayer and cache
|
// release the current exoplayer and cache
|
||||||
releasePlayer()
|
releasePlayer()
|
||||||
|
@ -322,6 +331,7 @@ class CS3IPlayer : IPlayer {
|
||||||
}
|
}
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
|
private var ytVideos: MutableMap<String, YtFile> = mutableMapOf()
|
||||||
private var simpleCache: SimpleCache? = null
|
private var simpleCache: SimpleCache? = null
|
||||||
|
|
||||||
var requestSubtitleUpdate: (() -> Unit)? = null
|
var requestSubtitleUpdate: (() -> Unit)? = null
|
||||||
|
@ -686,6 +696,14 @@ class CS3IPlayer : IPlayer {
|
||||||
isPlaying = exo.isPlaying
|
isPlaying = exo.isPlaying
|
||||||
}
|
}
|
||||||
|
|
||||||
|
when (playbackState) {
|
||||||
|
Player.STATE_READY -> {
|
||||||
|
onRenderFirst()
|
||||||
|
}
|
||||||
|
else -> {}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
if (playWhenReady) {
|
if (playWhenReady) {
|
||||||
when (playbackState) {
|
when (playbackState) {
|
||||||
Player.STATE_READY -> {
|
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() })
|
// 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() {
|
override fun onRenderedFirstFrame() {
|
||||||
updatedTime()
|
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()
|
super.onRenderedFirstFrame()
|
||||||
|
onRenderFirst()
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
} catch (e: Exception) {
|
} 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) {
|
private fun loadOfflinePlayer(context: Context, data: ExtractorUri) {
|
||||||
Log.i(TAG, "loadOfflinePlayer")
|
Log.i(TAG, "loadOfflinePlayer")
|
||||||
try {
|
try {
|
||||||
|
@ -829,9 +882,55 @@ class CS3IPlayer : IPlayer {
|
||||||
return Pair(subSources, activeSubtitles)
|
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) {
|
private fun loadOnlinePlayer(context: Context, link: ExtractorLink) {
|
||||||
Log.i(TAG, "loadOnlinePlayer")
|
Log.i(TAG, "loadOnlinePlayer $link")
|
||||||
try {
|
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
|
currentLink = link
|
||||||
|
|
||||||
if (ignoreSSL) {
|
if (ignoreSSL) {
|
||||||
|
|
|
@ -51,6 +51,21 @@ import com.lagradost.cloudstream3.utils.UIHelper.showSystemUI
|
||||||
import com.lagradost.cloudstream3.utils.UIHelper.toPx
|
import com.lagradost.cloudstream3.utils.UIHelper.toPx
|
||||||
import com.lagradost.cloudstream3.utils.Vector2
|
import com.lagradost.cloudstream3.utils.Vector2
|
||||||
import kotlinx.android.synthetic.main.player_custom_layout.*
|
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.*
|
import kotlin.math.*
|
||||||
|
|
||||||
const val MINIMUM_SEEK_TIME = 7000L // when swipe seeking
|
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
|
// All the UI Logic for the player
|
||||||
open class FullScreenPlayer : AbstractPlayerFragment() {
|
open class FullScreenPlayer : AbstractPlayerFragment() {
|
||||||
|
protected open var lockRotation = true
|
||||||
|
protected open var isFullScreenPlayer = true
|
||||||
|
|
||||||
// state of player UI
|
// state of player UI
|
||||||
protected var isShowing = false
|
protected var isShowing = false
|
||||||
protected var isLocked = false
|
protected var isLocked = false
|
||||||
|
@ -100,11 +118,11 @@ open class FullScreenPlayer : AbstractPlayerFragment() {
|
||||||
|
|
||||||
// screenWidth and screenHeight does always
|
// screenWidth and screenHeight does always
|
||||||
// refer to the screen while in landscape mode
|
// refer to the screen while in landscape mode
|
||||||
private val screenWidth: Int
|
protected val screenWidth: Int
|
||||||
get() {
|
get() {
|
||||||
return max(displayMetrics.widthPixels, displayMetrics.heightPixels)
|
return max(displayMetrics.widthPixels, displayMetrics.heightPixels)
|
||||||
}
|
}
|
||||||
private val screenHeight: Int
|
protected val screenHeight: Int
|
||||||
get() {
|
get() {
|
||||||
return min(displayMetrics.widthPixels, displayMetrics.heightPixels)
|
return min(displayMetrics.widthPixels, displayMetrics.heightPixels)
|
||||||
}
|
}
|
||||||
|
@ -159,7 +177,7 @@ open class FullScreenPlayer : AbstractPlayerFragment() {
|
||||||
animateLayoutChanges()
|
animateLayoutChanges()
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun animateLayoutChanges() {
|
protected fun animateLayoutChanges() {
|
||||||
if (isShowing) {
|
if (isShowing) {
|
||||||
updateUIVisibility()
|
updateUIVisibility()
|
||||||
} else {
|
} else {
|
||||||
|
@ -234,20 +252,25 @@ open class FullScreenPlayer : AbstractPlayerFragment() {
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onResume() {
|
override fun onResume() {
|
||||||
activity?.hideSystemUI()
|
if (isFullScreenPlayer) {
|
||||||
activity?.requestedOrientation = ActivityInfo.SCREEN_ORIENTATION_SENSOR_LANDSCAPE
|
activity?.hideSystemUI()
|
||||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P && fullscreenNotch) {
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P && fullscreenNotch) {
|
||||||
val params = activity?.window?.attributes
|
val params = activity?.window?.attributes
|
||||||
params?.layoutInDisplayCutoutMode = LAYOUT_IN_DISPLAY_CUTOUT_MODE_SHORT_EDGES
|
params?.layoutInDisplayCutoutMode = LAYOUT_IN_DISPLAY_CUTOUT_MODE_SHORT_EDGES
|
||||||
activity?.window?.attributes = params
|
activity?.window?.attributes = params
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
if (lockRotation)
|
||||||
|
activity?.requestedOrientation = ActivityInfo.SCREEN_ORIENTATION_SENSOR_LANDSCAPE
|
||||||
|
|
||||||
|
|
||||||
super.onResume()
|
super.onResume()
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onDestroy() {
|
override fun onDestroy() {
|
||||||
activity?.showSystemUI()
|
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
|
// simply resets brightness and notch settings that might have been overridden
|
||||||
val lp = activity?.window?.attributes
|
val lp = activity?.window?.attributes
|
||||||
|
@ -336,7 +359,8 @@ open class FullScreenPlayer : AbstractPlayerFragment() {
|
||||||
}
|
}
|
||||||
|
|
||||||
dialog.setOnDismissListener {
|
dialog.setOnDismissListener {
|
||||||
activity?.hideSystemUI()
|
if (isFullScreenPlayer)
|
||||||
|
activity?.hideSystemUI()
|
||||||
}
|
}
|
||||||
applyButton.setOnClickListener {
|
applyButton.setOnClickListener {
|
||||||
dialog.dismissSafe(activity)
|
dialog.dismissSafe(activity)
|
||||||
|
@ -374,9 +398,11 @@ open class FullScreenPlayer : AbstractPlayerFragment() {
|
||||||
act.getString(R.string.player_speed),
|
act.getString(R.string.player_speed),
|
||||||
false,
|
false,
|
||||||
{
|
{
|
||||||
activity?.hideSystemUI()
|
if (isFullScreenPlayer)
|
||||||
|
activity?.hideSystemUI()
|
||||||
}) { index ->
|
}) { index ->
|
||||||
activity?.hideSystemUI()
|
if (isFullScreenPlayer)
|
||||||
|
activity?.hideSystemUI()
|
||||||
setPlayBackSpeed(speedsNumbers[index])
|
setPlayBackSpeed(speedsNumbers[index])
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -455,9 +481,11 @@ open class FullScreenPlayer : AbstractPlayerFragment() {
|
||||||
private fun onClickChange() {
|
private fun onClickChange() {
|
||||||
isShowing = !isShowing
|
isShowing = !isShowing
|
||||||
if (isShowing) {
|
if (isShowing) {
|
||||||
|
player_intro_play?.isGone = true
|
||||||
autoHide()
|
autoHide()
|
||||||
}
|
}
|
||||||
activity?.hideSystemUI()
|
if (isFullScreenPlayer)
|
||||||
|
activity?.hideSystemUI()
|
||||||
animateLayoutChanges()
|
animateLayoutChanges()
|
||||||
player_pause_play?.requestFocus()
|
player_pause_play?.requestFocus()
|
||||||
}
|
}
|
||||||
|
@ -692,7 +720,7 @@ open class FullScreenPlayer : AbstractPlayerFragment() {
|
||||||
if (event == null || view == null) return false
|
if (event == null || view == null) return false
|
||||||
val currentTouch = Vector2(event.x, event.y)
|
val currentTouch = Vector2(event.x, event.y)
|
||||||
val startTouch = currentTouchStart
|
val startTouch = currentTouchStart
|
||||||
|
player_intro_play?.isGone = true
|
||||||
when (event.action) {
|
when (event.action) {
|
||||||
MotionEvent.ACTION_DOWN -> {
|
MotionEvent.ACTION_DOWN -> {
|
||||||
// validates if the touch is inside of the player area
|
// validates if the touch is inside of the player area
|
||||||
|
@ -717,7 +745,7 @@ open class FullScreenPlayer : AbstractPlayerFragment() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
MotionEvent.ACTION_UP -> {
|
MotionEvent.ACTION_UP -> {
|
||||||
if (isCurrentTouchValid && !isLocked) {
|
if (isCurrentTouchValid && !isLocked && isFullScreenPlayer) {
|
||||||
// seek time
|
// seek time
|
||||||
if (swipeHorizontalEnabled && currentTouchAction == TouchAction.Time) {
|
if (swipeHorizontalEnabled && currentTouchAction == TouchAction.Time) {
|
||||||
val startTime = currentTouchStartPlayerTime
|
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
|
if (doubleTapPauseEnabled) { // you can pause if your tap is in the middle of the screen
|
||||||
when {
|
when {
|
||||||
currentTouch.x < screenWidth / 2 - (DOUBLE_TAB_PAUSE_PERCENTAGE * screenWidth) -> {
|
currentTouch.x < screenWidth / 2 - (DOUBLE_TAB_PAUSE_PERCENTAGE * screenWidth) -> {
|
||||||
if (doubleTapEnabled)
|
if (doubleTapEnabled && isFullScreenPlayer)
|
||||||
rewind()
|
rewind()
|
||||||
}
|
}
|
||||||
currentTouch.x > screenWidth / 2 + (DOUBLE_TAB_PAUSE_PERCENTAGE * screenWidth) -> {
|
currentTouch.x > screenWidth / 2 + (DOUBLE_TAB_PAUSE_PERCENTAGE * screenWidth) -> {
|
||||||
if (doubleTapEnabled)
|
if (doubleTapEnabled && isFullScreenPlayer)
|
||||||
fastForward()
|
fastForward()
|
||||||
}
|
}
|
||||||
else -> {
|
else -> {
|
||||||
player.handleEvent(CSPlayerEvent.PlayPauseToggle)
|
player.handleEvent(CSPlayerEvent.PlayPauseToggle)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else if (doubleTapEnabled) {
|
} else if (doubleTapEnabled && isFullScreenPlayer) {
|
||||||
if (currentTouch.x < screenWidth / 2) {
|
if (currentTouch.x < screenWidth / 2) {
|
||||||
rewind()
|
rewind()
|
||||||
} else {
|
} else {
|
||||||
|
@ -798,7 +826,7 @@ open class FullScreenPlayer : AbstractPlayerFragment() {
|
||||||
}
|
}
|
||||||
MotionEvent.ACTION_MOVE -> {
|
MotionEvent.ACTION_MOVE -> {
|
||||||
// if current touch is valid
|
// if current touch is valid
|
||||||
if (startTouch != null && isCurrentTouchValid && !isLocked) {
|
if (startTouch != null && isCurrentTouchValid && !isLocked && isFullScreenPlayer) {
|
||||||
// action is unassigned and can therefore be assigned
|
// action is unassigned and can therefore be assigned
|
||||||
if (currentTouchAction == null) {
|
if (currentTouchAction == null) {
|
||||||
val diffFromStart = startTouch - currentTouch
|
val diffFromStart = startTouch - currentTouch
|
||||||
|
@ -1201,6 +1229,11 @@ open class FullScreenPlayer : AbstractPlayerFragment() {
|
||||||
showMirrorsDialogue()
|
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
|
// 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 ->
|
player_holder?.setOnTouchListener { callView, event ->
|
||||||
return@setOnTouchListener handleMotionEvent(callView, event)
|
return@setOnTouchListener handleMotionEvent(callView, event)
|
||||||
|
|
|
@ -98,6 +98,7 @@ interface IPlayer {
|
||||||
startPosition: Long? = null,
|
startPosition: Long? = null,
|
||||||
subtitles : Set<SubtitleData>,
|
subtitles : Set<SubtitleData>,
|
||||||
subtitle : SubtitleData?,
|
subtitle : SubtitleData?,
|
||||||
|
autoPlay : Boolean? = true
|
||||||
)
|
)
|
||||||
|
|
||||||
fun reloadPlayer(context: Context)
|
fun reloadPlayer(context: Context)
|
||||||
|
|
|
@ -28,7 +28,6 @@ import androidx.core.view.isGone
|
||||||
import androidx.core.view.isVisible
|
import androidx.core.view.isVisible
|
||||||
import androidx.core.widget.NestedScrollView
|
import androidx.core.widget.NestedScrollView
|
||||||
import androidx.core.widget.doOnTextChanged
|
import androidx.core.widget.doOnTextChanged
|
||||||
import androidx.fragment.app.Fragment
|
|
||||||
import androidx.lifecycle.ViewModelProvider
|
import androidx.lifecycle.ViewModelProvider
|
||||||
import androidx.preference.PreferenceManager
|
import androidx.preference.PreferenceManager
|
||||||
import androidx.recyclerview.widget.GridLayoutManager
|
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.popupMenuNoIconsAndNoStringRes
|
||||||
import com.lagradost.cloudstream3.utils.UIHelper.requestRW
|
import com.lagradost.cloudstream3.utils.UIHelper.requestRW
|
||||||
import com.lagradost.cloudstream3.utils.UIHelper.setImage
|
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.getFileName
|
||||||
import com.lagradost.cloudstream3.utils.VideoDownloadManager.sanitizeFilename
|
import com.lagradost.cloudstream3.utils.VideoDownloadManager.sanitizeFilename
|
||||||
import kotlinx.android.synthetic.main.fragment_result.*
|
import kotlinx.android.synthetic.main.fragment_result.*
|
||||||
|
@ -183,7 +181,7 @@ fun ResultEpisode.getWatchProgress(): Float {
|
||||||
return (getDisplayPosition() / 1000).toFloat() / (duration / 1000).toFloat()
|
return (getDisplayPosition() / 1000).toFloat() / (duration / 1000).toFloat()
|
||||||
}
|
}
|
||||||
|
|
||||||
class ResultFragment : Fragment(), PanelsChildGestureRegionObserver.GestureRegionsListener {
|
class ResultFragment : ResultTrailerPlayer() {
|
||||||
companion object {
|
companion object {
|
||||||
const val URL_BUNDLE = "url"
|
const val URL_BUNDLE = "url"
|
||||||
const val API_NAME_BUNDLE = "apiName"
|
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))
|
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>?) {
|
private fun setActors(actors: List<ActorData>?) {
|
||||||
if (actors.isNullOrEmpty()) {
|
if (actors.isNullOrEmpty()) {
|
||||||
result_cast_text?.isVisible = false
|
result_cast_text?.isVisible = false
|
||||||
|
@ -777,7 +822,7 @@ class ResultFragment : Fragment(), PanelsChildGestureRegionObserver.GestureRegio
|
||||||
} else if (dy < -5) {
|
} else if (dy < -5) {
|
||||||
result_bookmark_fab?.extend()
|
result_bookmark_fab?.extend()
|
||||||
}
|
}
|
||||||
result_poster_blur_holder?.translationY = -scrollY.toFloat()
|
//result_poster_blur_holder?.translationY = -scrollY.toFloat()
|
||||||
})
|
})
|
||||||
|
|
||||||
result_back.setOnClickListener {
|
result_back.setOnClickListener {
|
||||||
|
@ -1727,6 +1772,8 @@ class ResultFragment : Fragment(), PanelsChildGestureRegionObserver.GestureRegio
|
||||||
setRecommendations(d.recommendations, null)
|
setRecommendations(d.recommendations, null)
|
||||||
setActors(d.actors)
|
setActors(d.actors)
|
||||||
|
|
||||||
|
setTrailers(d.trailers)
|
||||||
|
|
||||||
if (syncModel.addSyncs(d.syncData)) {
|
if (syncModel.addSyncs(d.syncData)) {
|
||||||
syncModel.updateMetaAndUser()
|
syncModel.updateMetaAndUser()
|
||||||
syncModel.updateSynced()
|
syncModel.updateSynced()
|
||||||
|
@ -1739,7 +1786,7 @@ class ResultFragment : Fragment(), PanelsChildGestureRegionObserver.GestureRegio
|
||||||
val posterImageLink = d.posterUrl
|
val posterImageLink = d.posterUrl
|
||||||
if (!posterImageLink.isNullOrEmpty()) {
|
if (!posterImageLink.isNullOrEmpty()) {
|
||||||
result_poster?.setImage(posterImageLink, d.posterHeaders)
|
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
|
//Full screen view of Poster image
|
||||||
if (context?.isTrueTvSettings() == false) // Poster not clickable on tv
|
if (context?.isTrueTvSettings() == false) // Poster not clickable on tv
|
||||||
result_poster_holder?.setOnClickListener {
|
result_poster_holder?.setOnClickListener {
|
||||||
|
@ -1768,7 +1815,7 @@ class ResultFragment : Fragment(), PanelsChildGestureRegionObserver.GestureRegio
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
result_poster?.setImageResource(R.drawable.default_cover)
|
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
|
result_poster_holder?.visibility = VISIBLE
|
||||||
|
|
|
@ -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>) {}
|
||||||
|
}
|
|
@ -117,38 +117,13 @@
|
||||||
android:textColor="?attr/textColor" />
|
android:textColor="?attr/textColor" />
|
||||||
</LinearLayout>
|
</LinearLayout>
|
||||||
|
|
||||||
<FrameLayout
|
<LinearLayout
|
||||||
android:id="@+id/result_finish_loading"
|
android:id="@+id/result_finish_loading"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="match_parent"
|
android:layout_height="match_parent"
|
||||||
android:visibility="gone"
|
android:visibility="gone"
|
||||||
tools:visibility="visible">
|
tools:visibility="visible"
|
||||||
|
android:orientation="vertical">
|
||||||
<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>
|
|
||||||
|
|
||||||
<androidx.core.widget.NestedScrollView
|
<androidx.core.widget.NestedScrollView
|
||||||
android:id="@+id/result_scroll"
|
android:id="@+id/result_scroll"
|
||||||
|
@ -163,6 +138,8 @@
|
||||||
android:background="?attr/primaryBlackBackground"
|
android:background="?attr/primaryBlackBackground"
|
||||||
android:orientation="vertical">
|
android:orientation="vertical">
|
||||||
|
|
||||||
|
<include layout="@layout/fragment_trailer" />
|
||||||
|
|
||||||
<!--
|
<!--
|
||||||
<FrameLayout
|
<FrameLayout
|
||||||
android:background="?attr/primaryGrayBackground"
|
android:background="?attr/primaryGrayBackground"
|
||||||
|
@ -399,6 +376,7 @@
|
||||||
tools:text="Bookmark"
|
tools:text="Bookmark"
|
||||||
tools:visibility="visible" />
|
tools:visibility="visible" />
|
||||||
|
|
||||||
|
|
||||||
<TextView
|
<TextView
|
||||||
android:id="@+id/result_cast_text"
|
android:id="@+id/result_cast_text"
|
||||||
android:layout_width="wrap_content"
|
android:layout_width="wrap_content"
|
||||||
|
@ -855,7 +833,7 @@
|
||||||
</LinearLayout>
|
</LinearLayout>
|
||||||
</androidx.core.widget.NestedScrollView>
|
</androidx.core.widget.NestedScrollView>
|
||||||
|
|
||||||
</FrameLayout>
|
</LinearLayout>
|
||||||
|
|
||||||
|
|
||||||
<!--
|
<!--
|
||||||
|
|
33
app/src/main/res/layout/fragment_trailer.xml
Normal file
33
app/src/main/res/layout/fragment_trailer.xml
Normal 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>
|
330
app/src/main/res/layout/trailer_custom_layout.xml
Normal file
330
app/src/main/res/layout/trailer_custom_layout.xml
Normal 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>
|
|
@ -530,4 +530,5 @@
|
||||||
<string name="subtitles_remove_captions">Remove closed captions from subtitles</string>
|
<string name="subtitles_remove_captions">Remove closed captions from subtitles</string>
|
||||||
<string name="subtitles_remove_bloat">Remove bloat from subtitles</string>
|
<string name="subtitles_remove_bloat">Remove bloat from subtitles</string>
|
||||||
<string name="extras">Extras</string>
|
<string name="extras">Extras</string>
|
||||||
|
<string name="trailer">Trailer</string>
|
||||||
</resources>
|
</resources>
|
||||||
|
|
Loading…
Reference in a new issue