forked from recloudstream/cloudstream
		
	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…
	
	Add table
		Add a link
		
	
		Reference in a new issue