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
|
||||
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?) {
|
||||
// TODO add imdb sync
|
||||
}
|
||||
|
|
|
@ -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(
|
||||
|
|
|
@ -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())
|
||||
|
|
|
@ -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 ->
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -98,6 +98,7 @@ interface IPlayer {
|
|||
startPosition: Long? = null,
|
||||
subtitles : Set<SubtitleData>,
|
||||
subtitle : SubtitleData?,
|
||||
autoPlay : Boolean? = true
|
||||
)
|
||||
|
||||
fun reloadPlayer(context: Context)
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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" />
|
||||
</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>
|
||||
|
||||
|
||||
<!--
|
||||
|
|
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_bloat">Remove bloat from subtitles</string>
|
||||
<string name="extras">Extras</string>
|
||||
<string name="trailer">Trailer</string>
|
||||
</resources>
|
||||
|
|
Loading…
Reference in a new issue