forked from recloudstream/cloudstream
aniskip groundwork
This commit is contained in:
parent
f84259f898
commit
7619b7e9d9
17 changed files with 409 additions and 121 deletions
|
@ -337,6 +337,9 @@ object CommonActivity {
|
||||||
KeyEvent.KEYCODE_C, KeyEvent.KEYCODE_NUMPAD_4, KeyEvent.KEYCODE_4 -> {
|
KeyEvent.KEYCODE_C, KeyEvent.KEYCODE_NUMPAD_4, KeyEvent.KEYCODE_4 -> {
|
||||||
PlayerEventType.SkipOp
|
PlayerEventType.SkipOp
|
||||||
}
|
}
|
||||||
|
KeyEvent.KEYCODE_V, KeyEvent.KEYCODE_NUMPAD_5, KeyEvent.KEYCODE_5 -> {
|
||||||
|
PlayerEventType.SkipOp
|
||||||
|
}
|
||||||
KeyEvent.KEYCODE_MEDIA_PLAY_PAUSE, KeyEvent.KEYCODE_P, KeyEvent.KEYCODE_SPACE, KeyEvent.KEYCODE_NUMPAD_ENTER, KeyEvent.KEYCODE_ENTER -> { // space is not captured due to navigation
|
KeyEvent.KEYCODE_MEDIA_PLAY_PAUSE, KeyEvent.KEYCODE_P, KeyEvent.KEYCODE_SPACE, KeyEvent.KEYCODE_NUMPAD_ENTER, KeyEvent.KEYCODE_ENTER -> { // space is not captured due to navigation
|
||||||
PlayerEventType.PlayPauseToggle
|
PlayerEventType.PlayPauseToggle
|
||||||
}
|
}
|
||||||
|
|
|
@ -88,6 +88,9 @@ import kotlinx.android.synthetic.main.activity_main.*
|
||||||
import kotlinx.android.synthetic.main.fragment_result_swipe.*
|
import kotlinx.android.synthetic.main.fragment_result_swipe.*
|
||||||
import kotlinx.coroutines.sync.Mutex
|
import kotlinx.coroutines.sync.Mutex
|
||||||
import kotlinx.coroutines.sync.withLock
|
import kotlinx.coroutines.sync.withLock
|
||||||
|
import okhttp3.ConnectionSpec
|
||||||
|
import okhttp3.OkHttpClient
|
||||||
|
import okhttp3.internal.applyConnectionSpec
|
||||||
import java.io.File
|
import java.io.File
|
||||||
import java.net.URI
|
import java.net.URI
|
||||||
import java.nio.charset.Charset
|
import java.nio.charset.Charset
|
||||||
|
@ -179,6 +182,7 @@ var app = Requests(responseParser = object : ResponseParser {
|
||||||
}
|
}
|
||||||
}).apply {
|
}).apply {
|
||||||
defaultHeaders = mapOf("user-agent" to USER_AGENT)
|
defaultHeaders = mapOf("user-agent" to USER_AGENT)
|
||||||
|
//baseClient = baseClient.newBuilder().connectionSpecs(listOf(ConnectionSpec.COMPATIBLE_TLS)).build()
|
||||||
}
|
}
|
||||||
|
|
||||||
class MainActivity : AppCompatActivity(), ColorPickerDialogListener {
|
class MainActivity : AppCompatActivity(), ColorPickerDialogListener {
|
||||||
|
|
|
@ -6,10 +6,8 @@ import com.lagradost.cloudstream3.R
|
||||||
import com.lagradost.cloudstream3.USER_AGENT
|
import com.lagradost.cloudstream3.USER_AGENT
|
||||||
import com.lagradost.nicehttp.Requests
|
import com.lagradost.nicehttp.Requests
|
||||||
import com.lagradost.nicehttp.ignoreAllSSLErrors
|
import com.lagradost.nicehttp.ignoreAllSSLErrors
|
||||||
import okhttp3.Cache
|
import okhttp3.*
|
||||||
import okhttp3.Headers
|
|
||||||
import okhttp3.Headers.Companion.toHeaders
|
import okhttp3.Headers.Companion.toHeaders
|
||||||
import okhttp3.OkHttpClient
|
|
||||||
import java.io.File
|
import java.io.File
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -38,6 +38,7 @@ import com.lagradost.cloudstream3.ui.subtitles.SaveCaptionStyle
|
||||||
import com.lagradost.cloudstream3.ui.subtitles.SubtitlesFragment
|
import com.lagradost.cloudstream3.ui.subtitles.SubtitlesFragment
|
||||||
import com.lagradost.cloudstream3.utils.AppUtils
|
import com.lagradost.cloudstream3.utils.AppUtils
|
||||||
import com.lagradost.cloudstream3.utils.AppUtils.requestLocalAudioFocus
|
import com.lagradost.cloudstream3.utils.AppUtils.requestLocalAudioFocus
|
||||||
|
import com.lagradost.cloudstream3.utils.EpisodeSkip
|
||||||
import com.lagradost.cloudstream3.utils.UIHelper
|
import com.lagradost.cloudstream3.utils.UIHelper
|
||||||
import com.lagradost.cloudstream3.utils.UIHelper.hideSystemUI
|
import com.lagradost.cloudstream3.utils.UIHelper.hideSystemUI
|
||||||
import com.lagradost.cloudstream3.utils.UIHelper.popCurrentPage
|
import com.lagradost.cloudstream3.utils.UIHelper.popCurrentPage
|
||||||
|
@ -103,6 +104,10 @@ abstract class AbstractPlayerFragment(
|
||||||
throw NotImplementedError()
|
throw NotImplementedError()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
open fun onTimestamp(timestamp: EpisodeSkip.SkipStamp) {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
open fun exitedPipMode() {
|
open fun exitedPipMode() {
|
||||||
throw NotImplementedError()
|
throw NotImplementedError()
|
||||||
}
|
}
|
||||||
|
@ -373,7 +378,8 @@ abstract class AbstractPlayerFragment(
|
||||||
),
|
),
|
||||||
subtitlesUpdates = ::subtitlesChanged,
|
subtitlesUpdates = ::subtitlesChanged,
|
||||||
embeddedSubtitlesFetched = ::embeddedSubtitlesFetched,
|
embeddedSubtitlesFetched = ::embeddedSubtitlesFetched,
|
||||||
onTracksInfoChanged = ::onTracksInfoChanged
|
onTracksInfoChanged = ::onTracksInfoChanged,
|
||||||
|
onTimestampInvoked = ::onTimestamp
|
||||||
)
|
)
|
||||||
|
|
||||||
if (player is CS3IPlayer) {
|
if (player is CS3IPlayer) {
|
||||||
|
|
|
@ -18,7 +18,10 @@ import com.google.android.exoplayer2.trackselection.DefaultTrackSelector
|
||||||
import com.google.android.exoplayer2.trackselection.TrackSelectionOverride
|
import com.google.android.exoplayer2.trackselection.TrackSelectionOverride
|
||||||
import com.google.android.exoplayer2.trackselection.TrackSelector
|
import com.google.android.exoplayer2.trackselection.TrackSelector
|
||||||
import com.google.android.exoplayer2.ui.SubtitleView
|
import com.google.android.exoplayer2.ui.SubtitleView
|
||||||
import com.google.android.exoplayer2.upstream.*
|
import com.google.android.exoplayer2.upstream.DataSource
|
||||||
|
import com.google.android.exoplayer2.upstream.DefaultDataSourceFactory
|
||||||
|
import com.google.android.exoplayer2.upstream.DefaultHttpDataSource
|
||||||
|
import com.google.android.exoplayer2.upstream.HttpDataSource
|
||||||
import com.google.android.exoplayer2.upstream.cache.CacheDataSource
|
import com.google.android.exoplayer2.upstream.cache.CacheDataSource
|
||||||
import com.google.android.exoplayer2.upstream.cache.LeastRecentlyUsedCacheEvictor
|
import com.google.android.exoplayer2.upstream.cache.LeastRecentlyUsedCacheEvictor
|
||||||
import com.google.android.exoplayer2.upstream.cache.SimpleCache
|
import com.google.android.exoplayer2.upstream.cache.SimpleCache
|
||||||
|
@ -32,6 +35,7 @@ import com.lagradost.cloudstream3.app
|
||||||
import com.lagradost.cloudstream3.mvvm.logError
|
import com.lagradost.cloudstream3.mvvm.logError
|
||||||
import com.lagradost.cloudstream3.mvvm.normalSafeApiCall
|
import com.lagradost.cloudstream3.mvvm.normalSafeApiCall
|
||||||
import com.lagradost.cloudstream3.ui.subtitles.SaveCaptionStyle
|
import com.lagradost.cloudstream3.ui.subtitles.SaveCaptionStyle
|
||||||
|
import com.lagradost.cloudstream3.utils.EpisodeSkip
|
||||||
import com.lagradost.cloudstream3.utils.ExtractorLink
|
import com.lagradost.cloudstream3.utils.ExtractorLink
|
||||||
import com.lagradost.cloudstream3.utils.ExtractorLinkPlayList
|
import com.lagradost.cloudstream3.utils.ExtractorLinkPlayList
|
||||||
import com.lagradost.cloudstream3.utils.ExtractorUri
|
import com.lagradost.cloudstream3.utils.ExtractorUri
|
||||||
|
@ -113,6 +117,7 @@ class CS3IPlayer : IPlayer {
|
||||||
private var playerUpdated: ((Any?) -> Unit)? = null
|
private var playerUpdated: ((Any?) -> Unit)? = null
|
||||||
private var embeddedSubtitlesFetched: ((List<SubtitleData>) -> Unit)? = null
|
private var embeddedSubtitlesFetched: ((List<SubtitleData>) -> Unit)? = null
|
||||||
private var onTracksInfoChanged: (() -> Unit)? = null
|
private var onTracksInfoChanged: (() -> Unit)? = null
|
||||||
|
private var onTimestampInvoked: ((EpisodeSkip.SkipStamp) -> Unit)? = null
|
||||||
|
|
||||||
override fun releaseCallbacks() {
|
override fun releaseCallbacks() {
|
||||||
playerUpdated = null
|
playerUpdated = null
|
||||||
|
@ -126,6 +131,7 @@ class CS3IPlayer : IPlayer {
|
||||||
prevEpisode = null
|
prevEpisode = null
|
||||||
subtitlesUpdates = null
|
subtitlesUpdates = null
|
||||||
onTracksInfoChanged = null
|
onTracksInfoChanged = null
|
||||||
|
onTimestampInvoked = null
|
||||||
requestSubtitleUpdate = null
|
requestSubtitleUpdate = null
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -142,6 +148,7 @@ class CS3IPlayer : IPlayer {
|
||||||
subtitlesUpdates: (() -> Unit)?,
|
subtitlesUpdates: (() -> Unit)?,
|
||||||
embeddedSubtitlesFetched: ((List<SubtitleData>) -> Unit)?,
|
embeddedSubtitlesFetched: ((List<SubtitleData>) -> Unit)?,
|
||||||
onTracksInfoChanged: (() -> Unit)?,
|
onTracksInfoChanged: (() -> Unit)?,
|
||||||
|
onTimestampInvoked: ((EpisodeSkip.SkipStamp) -> Unit)?,
|
||||||
) {
|
) {
|
||||||
this.playerUpdated = playerUpdated
|
this.playerUpdated = playerUpdated
|
||||||
this.updateIsPlaying = updateIsPlaying
|
this.updateIsPlaying = updateIsPlaying
|
||||||
|
@ -155,6 +162,7 @@ class CS3IPlayer : IPlayer {
|
||||||
this.subtitlesUpdates = subtitlesUpdates
|
this.subtitlesUpdates = subtitlesUpdates
|
||||||
this.embeddedSubtitlesFetched = embeddedSubtitlesFetched
|
this.embeddedSubtitlesFetched = embeddedSubtitlesFetched
|
||||||
this.onTracksInfoChanged = onTracksInfoChanged
|
this.onTracksInfoChanged = onTracksInfoChanged
|
||||||
|
this.onTimestampInvoked = onTimestampInvoked
|
||||||
}
|
}
|
||||||
|
|
||||||
// I know, this is not a perfect solution, however it works for fixing subs
|
// I know, this is not a perfect solution, however it works for fixing subs
|
||||||
|
@ -789,6 +797,16 @@ class CS3IPlayer : IPlayer {
|
||||||
CSPlayerEvent.SeekBack -> seekTime(-seekActionTime)
|
CSPlayerEvent.SeekBack -> seekTime(-seekActionTime)
|
||||||
CSPlayerEvent.NextEpisode -> nextEpisode?.invoke()
|
CSPlayerEvent.NextEpisode -> nextEpisode?.invoke()
|
||||||
CSPlayerEvent.PrevEpisode -> prevEpisode?.invoke()
|
CSPlayerEvent.PrevEpisode -> prevEpisode?.invoke()
|
||||||
|
CSPlayerEvent.SkipCurrentChapter -> {
|
||||||
|
//val dur = this@CS3IPlayer.getDuration() ?: return@apply
|
||||||
|
val pos = this@CS3IPlayer.getPosition() ?: return@apply
|
||||||
|
for (lastTimeStamp in lastTimeStamps) {
|
||||||
|
if(lastTimeStamp.startMs <= pos && pos < lastTimeStamp.endMs) {
|
||||||
|
seekTo(lastTimeStamp.endMs)
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
|
@ -1007,6 +1025,21 @@ class CS3IPlayer : IPlayer {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private var lastTimeStamps: List<EpisodeSkip.SkipStamp> = emptyList()
|
||||||
|
override fun addTimeStamps(timeStamps: List<EpisodeSkip.SkipStamp>) {
|
||||||
|
lastTimeStamps = timeStamps
|
||||||
|
timeStamps.forEach { timestamp ->
|
||||||
|
exoPlayer?.createMessage { _, payload ->
|
||||||
|
|
||||||
|
}
|
||||||
|
?.setLooper(Looper.getMainLooper())
|
||||||
|
?.setPosition(timestamp.startMs)
|
||||||
|
// .setPayload(customPayloadData)
|
||||||
|
?.setDeleteAfterDelivery(false)
|
||||||
|
?.send()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fun onRenderFirst() {
|
fun onRenderFirst() {
|
||||||
if (!hasUsedFirstRender) { // this insures that we only call this once per player load
|
if (!hasUsedFirstRender) { // this insures that we only call this once per player load
|
||||||
Log.i(TAG, "Rendered first frame")
|
Log.i(TAG, "Rendered first frame")
|
||||||
|
|
|
@ -1141,6 +1141,9 @@ open class FullScreenPlayer : AbstractPlayerFragment() {
|
||||||
PlayerEventType.Play -> {
|
PlayerEventType.Play -> {
|
||||||
player.handleEvent(CSPlayerEvent.Play)
|
player.handleEvent(CSPlayerEvent.Play)
|
||||||
}
|
}
|
||||||
|
PlayerEventType.SkipCurrentChapter -> {
|
||||||
|
player.handleEvent(CSPlayerEvent.SkipCurrentChapter)
|
||||||
|
}
|
||||||
PlayerEventType.Resize -> {
|
PlayerEventType.Resize -> {
|
||||||
nextResize()
|
nextResize()
|
||||||
}
|
}
|
||||||
|
@ -1254,6 +1257,10 @@ open class FullScreenPlayer : AbstractPlayerFragment() {
|
||||||
player.handleEvent(CSPlayerEvent.PlayPauseToggle)
|
player.handleEvent(CSPlayerEvent.PlayPauseToggle)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
skip_chapter_button?.setOnClickListener {
|
||||||
|
player.handleEvent(CSPlayerEvent.SkipCurrentChapter)
|
||||||
|
}
|
||||||
|
|
||||||
// init clicks
|
// init clicks
|
||||||
player_resize_btt?.setOnClickListener {
|
player_resize_btt?.setOnClickListener {
|
||||||
autoHide()
|
autoHide()
|
||||||
|
|
|
@ -36,8 +36,8 @@ import com.lagradost.cloudstream3.ui.player.PlayerSubtitleHelper.Companion.toSub
|
||||||
import com.lagradost.cloudstream3.ui.result.ResultEpisode
|
import com.lagradost.cloudstream3.ui.result.ResultEpisode
|
||||||
import com.lagradost.cloudstream3.ui.result.ResultFragment
|
import com.lagradost.cloudstream3.ui.result.ResultFragment
|
||||||
import com.lagradost.cloudstream3.ui.result.SyncViewModel
|
import com.lagradost.cloudstream3.ui.result.SyncViewModel
|
||||||
|
import com.lagradost.cloudstream3.ui.result.setText
|
||||||
import com.lagradost.cloudstream3.ui.settings.SettingsFragment.Companion.isTvSettings
|
import com.lagradost.cloudstream3.ui.settings.SettingsFragment.Companion.isTvSettings
|
||||||
import com.lagradost.cloudstream3.ui.subtitles.SubtitlesFragment
|
|
||||||
import com.lagradost.cloudstream3.ui.subtitles.SubtitlesFragment.Companion.getAutoSelectLanguageISO639_1
|
import com.lagradost.cloudstream3.ui.subtitles.SubtitlesFragment.Companion.getAutoSelectLanguageISO639_1
|
||||||
import com.lagradost.cloudstream3.utils.*
|
import com.lagradost.cloudstream3.utils.*
|
||||||
import com.lagradost.cloudstream3.utils.Coroutines.ioSafe
|
import com.lagradost.cloudstream3.utils.Coroutines.ioSafe
|
||||||
|
@ -58,7 +58,6 @@ import kotlinx.android.synthetic.main.player_select_source_and_subs.*
|
||||||
import kotlinx.android.synthetic.main.player_select_source_and_subs.subtitles_click_settings
|
import kotlinx.android.synthetic.main.player_select_source_and_subs.subtitles_click_settings
|
||||||
import kotlinx.android.synthetic.main.player_select_tracks.*
|
import kotlinx.android.synthetic.main.player_select_tracks.*
|
||||||
import kotlinx.coroutines.Job
|
import kotlinx.coroutines.Job
|
||||||
import kotlinx.coroutines.delay
|
|
||||||
|
|
||||||
class GeneratorPlayer : FullScreenPlayer() {
|
class GeneratorPlayer : FullScreenPlayer() {
|
||||||
companion object {
|
companion object {
|
||||||
|
@ -165,6 +164,7 @@ class GeneratorPlayer : FullScreenPlayer() {
|
||||||
isActive = true
|
isActive = true
|
||||||
setPlayerDimen(null)
|
setPlayerDimen(null)
|
||||||
setTitle()
|
setTitle()
|
||||||
|
hasRequestedStamps = false
|
||||||
|
|
||||||
loadExtractorJob(link.first)
|
loadExtractorJob(link.first)
|
||||||
// load player
|
// load player
|
||||||
|
@ -878,7 +878,7 @@ class GeneratorPlayer : FullScreenPlayer() {
|
||||||
}
|
}
|
||||||
|
|
||||||
var maxEpisodeSet: Int? = null
|
var maxEpisodeSet: Int? = null
|
||||||
|
var hasRequestedStamps: Boolean = false
|
||||||
override fun playerPositionChanged(posDur: Pair<Long, Long>) {
|
override fun playerPositionChanged(posDur: Pair<Long, Long>) {
|
||||||
// Don't save livestream data
|
// Don't save livestream data
|
||||||
if ((currentMeta as? ResultEpisode)?.tvType?.isLiveStream() == true) return
|
if ((currentMeta as? ResultEpisode)?.tvType?.isLiveStream() == true) return
|
||||||
|
@ -888,10 +888,15 @@ class GeneratorPlayer : FullScreenPlayer() {
|
||||||
|
|
||||||
val (position, duration) = posDur
|
val (position, duration) = posDur
|
||||||
if (duration == 0L) return // idk how you achieved this, but div by zero crash
|
if (duration == 0L) return // idk how you achieved this, but div by zero crash
|
||||||
|
if (!hasRequestedStamps) {
|
||||||
|
hasRequestedStamps = true
|
||||||
|
viewModel.loadStamps(duration)
|
||||||
|
}
|
||||||
|
|
||||||
viewModel.getId()?.let {
|
viewModel.getId()?.let {
|
||||||
DataStoreHelper.setViewPos(it, position, duration)
|
DataStoreHelper.setViewPos(it, position, duration)
|
||||||
}
|
}
|
||||||
|
|
||||||
val percentage = position * 100L / duration
|
val percentage = position * 100L / duration
|
||||||
|
|
||||||
val nextEp = percentage >= NEXT_WATCH_EPISODE_PERCENTAGE
|
val nextEp = percentage >= NEXT_WATCH_EPISODE_PERCENTAGE
|
||||||
|
@ -1174,6 +1179,10 @@ class GeneratorPlayer : FullScreenPlayer() {
|
||||||
return super.onCreateView(inflater, container, savedInstanceState)
|
return super.onCreateView(inflater, container, savedInstanceState)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override fun onTimestamp(timestamp: EpisodeSkip.SkipStamp) {
|
||||||
|
skip_chapter_button.setText(timestamp.uiText)
|
||||||
|
}
|
||||||
|
|
||||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||||
super.onViewCreated(view, savedInstanceState)
|
super.onViewCreated(view, savedInstanceState)
|
||||||
var langFilterList = listOf<String>()
|
var langFilterList = listOf<String>()
|
||||||
|
@ -1203,7 +1212,7 @@ class GeneratorPlayer : FullScreenPlayer() {
|
||||||
|
|
||||||
sync.updateUserData()
|
sync.updateUserData()
|
||||||
|
|
||||||
preferredAutoSelectSubtitles = SubtitlesFragment.getAutoSelectLanguageISO639_1()
|
preferredAutoSelectSubtitles = getAutoSelectLanguageISO639_1()
|
||||||
|
|
||||||
if (currentSelectedLink == null) {
|
if (currentSelectedLink == null) {
|
||||||
viewModel.loadLinks()
|
viewModel.loadLinks()
|
||||||
|
@ -1218,6 +1227,10 @@ class GeneratorPlayer : FullScreenPlayer() {
|
||||||
activity?.popCurrentPage()
|
activity?.popCurrentPage()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
observe(viewModel.currentStamps) { stamps ->
|
||||||
|
player.addTimeStamps(stamps)
|
||||||
|
}
|
||||||
|
|
||||||
observe(viewModel.loadingLinks) {
|
observe(viewModel.loadingLinks) {
|
||||||
when (it) {
|
when (it) {
|
||||||
is Resource.Loading -> {
|
is Resource.Loading -> {
|
||||||
|
|
|
@ -2,6 +2,7 @@ package com.lagradost.cloudstream3.ui.player
|
||||||
|
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import com.lagradost.cloudstream3.ui.subtitles.SaveCaptionStyle
|
import com.lagradost.cloudstream3.ui.subtitles.SaveCaptionStyle
|
||||||
|
import com.lagradost.cloudstream3.utils.EpisodeSkip
|
||||||
import com.lagradost.cloudstream3.utils.ExtractorLink
|
import com.lagradost.cloudstream3.utils.ExtractorLink
|
||||||
import com.lagradost.cloudstream3.utils.ExtractorUri
|
import com.lagradost.cloudstream3.utils.ExtractorUri
|
||||||
|
|
||||||
|
@ -12,9 +13,9 @@ enum class PlayerEventType(val value: Int) {
|
||||||
SeekForward(2),
|
SeekForward(2),
|
||||||
SeekBack(3),
|
SeekBack(3),
|
||||||
|
|
||||||
//SkipCurrentChapter(4),
|
SkipCurrentChapter(4),
|
||||||
NextEpisode(5),
|
NextEpisode(5),
|
||||||
PrevEpisode(5),
|
PrevEpisode(6),
|
||||||
PlayPauseToggle(7),
|
PlayPauseToggle(7),
|
||||||
ToggleMute(8),
|
ToggleMute(8),
|
||||||
Lock(9),
|
Lock(9),
|
||||||
|
@ -32,7 +33,7 @@ enum class CSPlayerEvent(val value: Int) {
|
||||||
SeekForward(2),
|
SeekForward(2),
|
||||||
SeekBack(3),
|
SeekBack(3),
|
||||||
|
|
||||||
//SkipCurrentChapter(4),
|
SkipCurrentChapter(4),
|
||||||
NextEpisode(5),
|
NextEpisode(5),
|
||||||
PrevEpisode(6),
|
PrevEpisode(6),
|
||||||
PlayPauseToggle(7),
|
PlayPauseToggle(7),
|
||||||
|
@ -54,7 +55,8 @@ interface Track {
|
||||||
**/
|
**/
|
||||||
val id: String?
|
val id: String?
|
||||||
val label: String?
|
val label: String?
|
||||||
// val isCurrentlyPlaying: Boolean
|
|
||||||
|
// val isCurrentlyPlaying: Boolean
|
||||||
val language: String?
|
val language: String?
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -124,6 +126,7 @@ interface IPlayer {
|
||||||
subtitlesUpdates: (() -> Unit)? = null, // callback from player to inform that subtitles have updated in some way
|
subtitlesUpdates: (() -> Unit)? = null, // callback from player to inform that subtitles have updated in some way
|
||||||
embeddedSubtitlesFetched: ((List<SubtitleData>) -> Unit)? = null, // callback from player to give all embedded subtitles
|
embeddedSubtitlesFetched: ((List<SubtitleData>) -> Unit)? = null, // callback from player to give all embedded subtitles
|
||||||
onTracksInfoChanged: (() -> Unit)? = null, // Callback when tracks are changed, used for UI changes
|
onTracksInfoChanged: (() -> Unit)? = null, // Callback when tracks are changed, used for UI changes
|
||||||
|
onTimestampInvoked: ((EpisodeSkip.SkipStamp) -> Unit)? = null, // Callback when timestamps appear
|
||||||
)
|
)
|
||||||
|
|
||||||
fun releaseCallbacks()
|
fun releaseCallbacks()
|
||||||
|
@ -131,6 +134,8 @@ interface IPlayer {
|
||||||
fun updateSubtitleStyle(style: SaveCaptionStyle)
|
fun updateSubtitleStyle(style: SaveCaptionStyle)
|
||||||
fun saveData()
|
fun saveData()
|
||||||
|
|
||||||
|
fun addTimeStamps(timeStamps: List<EpisodeSkip.SkipStamp>)
|
||||||
|
|
||||||
fun loadPlayer(
|
fun loadPlayer(
|
||||||
context: Context,
|
context: Context,
|
||||||
sameEpisode: Boolean,
|
sameEpisode: Boolean,
|
||||||
|
|
|
@ -9,10 +9,12 @@ import com.lagradost.cloudstream3.mvvm.Resource
|
||||||
import com.lagradost.cloudstream3.mvvm.launchSafe
|
import com.lagradost.cloudstream3.mvvm.launchSafe
|
||||||
import com.lagradost.cloudstream3.mvvm.normalSafeApiCall
|
import com.lagradost.cloudstream3.mvvm.normalSafeApiCall
|
||||||
import com.lagradost.cloudstream3.mvvm.safeApiCall
|
import com.lagradost.cloudstream3.mvvm.safeApiCall
|
||||||
|
import com.lagradost.cloudstream3.ui.result.ResultEpisode
|
||||||
|
import com.lagradost.cloudstream3.utils.Coroutines.ioSafe
|
||||||
|
import com.lagradost.cloudstream3.utils.EpisodeSkip
|
||||||
import com.lagradost.cloudstream3.utils.ExtractorLink
|
import com.lagradost.cloudstream3.utils.ExtractorLink
|
||||||
import com.lagradost.cloudstream3.utils.ExtractorUri
|
import com.lagradost.cloudstream3.utils.ExtractorUri
|
||||||
import kotlinx.coroutines.Job
|
import kotlinx.coroutines.Job
|
||||||
import kotlinx.coroutines.launch
|
|
||||||
|
|
||||||
class PlayerGeneratorViewModel : ViewModel() {
|
class PlayerGeneratorViewModel : ViewModel() {
|
||||||
companion object {
|
companion object {
|
||||||
|
@ -30,6 +32,9 @@ class PlayerGeneratorViewModel : ViewModel() {
|
||||||
private val _loadingLinks = MutableLiveData<Resource<Boolean?>>()
|
private val _loadingLinks = MutableLiveData<Resource<Boolean?>>()
|
||||||
val loadingLinks: LiveData<Resource<Boolean?>> = _loadingLinks
|
val loadingLinks: LiveData<Resource<Boolean?>> = _loadingLinks
|
||||||
|
|
||||||
|
private val _currentStamps = MutableLiveData<List<EpisodeSkip.SkipStamp>>(emptyList())
|
||||||
|
val currentStamps: LiveData<List<EpisodeSkip.SkipStamp>> = _currentStamps
|
||||||
|
|
||||||
fun getId(): Int? {
|
fun getId(): Int? {
|
||||||
return generator?.getCurrentId()
|
return generator?.getCurrentId()
|
||||||
}
|
}
|
||||||
|
@ -113,10 +118,25 @@ class PlayerGeneratorViewModel : ViewModel() {
|
||||||
}
|
}
|
||||||
|
|
||||||
private var currentJob: Job? = null
|
private var currentJob: Job? = null
|
||||||
|
private var currentStampJob: Job? = null
|
||||||
|
|
||||||
|
fun loadStamps(duration: Long) {
|
||||||
|
println("Starting loadStamps with duration = $duration")
|
||||||
|
//currentStampJob?.cancel()
|
||||||
|
currentStampJob = ioSafe {
|
||||||
|
val meta = generator?.getCurrent()
|
||||||
|
val page = (generator as? RepoLinkGenerator?)?.page
|
||||||
|
if (page != null && meta is ResultEpisode) {
|
||||||
|
_currentStamps.postValue(listOf())
|
||||||
|
_currentStamps.postValue(EpisodeSkip.getStamps(page, meta, duration))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fun loadLinks(clearCache: Boolean = false, isCasting: Boolean = false) {
|
fun loadLinks(clearCache: Boolean = false, isCasting: Boolean = false) {
|
||||||
Log.i(TAG, "loadLinks")
|
Log.i(TAG, "loadLinks")
|
||||||
currentJob?.cancel()
|
currentJob?.cancel()
|
||||||
|
|
||||||
currentJob = viewModelScope.launchSafe {
|
currentJob = viewModelScope.launchSafe {
|
||||||
val currentLinks = mutableSetOf<Pair<ExtractorLink?, ExtractorUri?>>()
|
val currentLinks = mutableSetOf<Pair<ExtractorLink?, ExtractorUri?>>()
|
||||||
val currentSubs = mutableSetOf<SubtitleData>()
|
val currentSubs = mutableSetOf<SubtitleData>()
|
||||||
|
@ -142,5 +162,6 @@ class PlayerGeneratorViewModel : ViewModel() {
|
||||||
_currentLinks.postValue(currentLinks)
|
_currentLinks.postValue(currentLinks)
|
||||||
_currentSubs.postValue(currentSubs)
|
_currentSubs.postValue(currentSubs)
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -2,6 +2,7 @@ package com.lagradost.cloudstream3.ui.player
|
||||||
|
|
||||||
import android.util.Log
|
import android.util.Log
|
||||||
import com.lagradost.cloudstream3.APIHolder.getApiFromNameNull
|
import com.lagradost.cloudstream3.APIHolder.getApiFromNameNull
|
||||||
|
import com.lagradost.cloudstream3.LoadResponse
|
||||||
import com.lagradost.cloudstream3.ui.APIRepository
|
import com.lagradost.cloudstream3.ui.APIRepository
|
||||||
import com.lagradost.cloudstream3.ui.result.ResultEpisode
|
import com.lagradost.cloudstream3.ui.result.ResultEpisode
|
||||||
import com.lagradost.cloudstream3.utils.ExtractorLink
|
import com.lagradost.cloudstream3.utils.ExtractorLink
|
||||||
|
@ -11,7 +12,8 @@ import kotlin.math.min
|
||||||
|
|
||||||
class RepoLinkGenerator(
|
class RepoLinkGenerator(
|
||||||
private val episodes: List<ResultEpisode>,
|
private val episodes: List<ResultEpisode>,
|
||||||
private var currentIndex: Int = 0
|
private var currentIndex: Int = 0,
|
||||||
|
val page: LoadResponse? = null,
|
||||||
) : IGenerator {
|
) : IGenerator {
|
||||||
companion object {
|
companion object {
|
||||||
const val TAG = "RepoLink"
|
const val TAG = "RepoLink"
|
||||||
|
|
|
@ -416,7 +416,7 @@ class ResultViewModel2 : ViewModel() {
|
||||||
return this?.firstOrNull { it.season == season }
|
return this?.firstOrNull { it.season == season }
|
||||||
}
|
}
|
||||||
|
|
||||||
fun updateWatchStatus(currentResponse : LoadResponse, status: WatchType) {
|
fun updateWatchStatus(currentResponse: LoadResponse, status: WatchType) {
|
||||||
val currentId = currentResponse.getId()
|
val currentId = currentResponse.getId()
|
||||||
val resultPage = currentResponse
|
val resultPage = currentResponse
|
||||||
|
|
||||||
|
@ -793,7 +793,7 @@ class ResultViewModel2 : ViewModel() {
|
||||||
|
|
||||||
|
|
||||||
fun updateWatchStatus(status: WatchType) {
|
fun updateWatchStatus(status: WatchType) {
|
||||||
updateWatchStatus(currentResponse ?: return,status)
|
updateWatchStatus(currentResponse ?: return, status)
|
||||||
_watchStatus.postValue(status)
|
_watchStatus.postValue(status)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1681,10 +1681,10 @@ class ResultViewModel2 : ViewModel() {
|
||||||
preferDubStatus = indexer.dubStatus
|
preferDubStatus = indexer.dubStatus
|
||||||
|
|
||||||
generator = if (isMovie) {
|
generator = if (isMovie) {
|
||||||
getMovie()?.let { RepoLinkGenerator(listOf(it)) }
|
getMovie()?.let { RepoLinkGenerator(listOf(it), page = currentResponse) }
|
||||||
} else {
|
} else {
|
||||||
episodes?.let { list ->
|
episodes?.let { list ->
|
||||||
RepoLinkGenerator(list)
|
RepoLinkGenerator(list, page = currentResponse)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
114
app/src/main/java/com/lagradost/cloudstream3/utils/AniSkip.kt
Normal file
114
app/src/main/java/com/lagradost/cloudstream3/utils/AniSkip.kt
Normal file
|
@ -0,0 +1,114 @@
|
||||||
|
package com.lagradost.cloudstream3.utils
|
||||||
|
|
||||||
|
import android.util.Log
|
||||||
|
import androidx.annotation.StringRes
|
||||||
|
import com.fasterxml.jackson.databind.annotation.JsonSerialize
|
||||||
|
import com.lagradost.cloudstream3.*
|
||||||
|
import com.lagradost.cloudstream3.LoadResponse.Companion.getMalId
|
||||||
|
import com.lagradost.cloudstream3.mvvm.logError
|
||||||
|
import com.lagradost.cloudstream3.ui.result.ResultEpisode
|
||||||
|
import com.lagradost.cloudstream3.ui.result.txt
|
||||||
|
|
||||||
|
object EpisodeSkip {
|
||||||
|
data class SkipStamp(
|
||||||
|
@StringRes
|
||||||
|
private val name: Int,
|
||||||
|
val startMs: Long,
|
||||||
|
val endMs: Long,
|
||||||
|
) {
|
||||||
|
val uiText = txt(R.string.skip_type_format, txt(name))
|
||||||
|
}
|
||||||
|
|
||||||
|
private val cachedStamps = HashMap<Int, List<SkipStamp>>()
|
||||||
|
|
||||||
|
suspend fun getStamps(
|
||||||
|
data: LoadResponse,
|
||||||
|
episode: ResultEpisode,
|
||||||
|
episodeDurationMs: Long
|
||||||
|
): List<SkipStamp> {
|
||||||
|
cachedStamps[episode.id]?.let { list ->
|
||||||
|
return list
|
||||||
|
}
|
||||||
|
|
||||||
|
val out = mutableListOf<SkipStamp>()
|
||||||
|
println("CALLING WITH : ${data.syncData} $episode $episodeDurationMs")
|
||||||
|
if (data is AnimeLoadResponse && (data.type == TvType.Anime || data.type == TvType.OVA)) {
|
||||||
|
data.getMalId()?.toIntOrNull()?.let { malId ->
|
||||||
|
AniSkip.getResult(malId, episode.episode, episodeDurationMs)?.mapNotNull { stamp ->
|
||||||
|
val name = when (stamp.skipType) {
|
||||||
|
"op" -> R.string.skip_type_op
|
||||||
|
"ed" -> R.string.skip_type_ed
|
||||||
|
"recap" -> R.string.skip_type_recap
|
||||||
|
"mixed-ed" -> R.string.skip_type_mixed_ed
|
||||||
|
"mixed-op" -> R.string.skip_type_mixed_op
|
||||||
|
else -> null
|
||||||
|
} ?: return@mapNotNull null
|
||||||
|
SkipStamp(
|
||||||
|
name,
|
||||||
|
(stamp.interval.startTime * 1000.0).toLong(),
|
||||||
|
(stamp.interval.endTime * 1000.0).toLong()
|
||||||
|
)
|
||||||
|
}?.let { list ->
|
||||||
|
out.addAll(list)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (out.isNotEmpty())
|
||||||
|
cachedStamps[episode.id] = out
|
||||||
|
return out
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// taken from https://github.com/saikou-app/saikou/blob/3803f8a7a59b826ca193664d46af3a22bbc989f7/app/src/main/java/ani/saikou/others/AniSkip.kt
|
||||||
|
// the following is GPLv3 code https://github.com/saikou-app/saikou/blob/main/LICENSE.md
|
||||||
|
object AniSkip {
|
||||||
|
suspend fun getResult(malId: Int, episodeNumber: Int, episodeLength: Long): List<Stamp>? {
|
||||||
|
return try {
|
||||||
|
val url =
|
||||||
|
"https://api.aniskip.com/v2/skip-times/$malId/$episodeNumber?types[]=ed&types[]=mixed-ed&types[]=mixed-op&types[]=op&types[]=recap&episodeLength=${episodeLength / 1000L}"
|
||||||
|
println("URLLLL::::$url")
|
||||||
|
|
||||||
|
val a = app.get(url)
|
||||||
|
println("GOT RESPONSE:::.")
|
||||||
|
val res = a.parsed<AniSkipResponse>()
|
||||||
|
Log.i("AniSkip", "Response = $res")
|
||||||
|
if (res.found) res.results else null
|
||||||
|
} catch (t: Throwable) {
|
||||||
|
Log.i("AniSkip", "error = ${t.message}")
|
||||||
|
logError(t)
|
||||||
|
null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
data class AniSkipResponse(
|
||||||
|
@JsonSerialize val found: Boolean,
|
||||||
|
@JsonSerialize val results: List<Stamp>?,
|
||||||
|
@JsonSerialize val message: String?,
|
||||||
|
@JsonSerialize val statusCode: Int
|
||||||
|
)
|
||||||
|
|
||||||
|
data class Stamp(
|
||||||
|
@JsonSerialize val interval: AniSkipInterval,
|
||||||
|
@JsonSerialize val skipType: String,
|
||||||
|
@JsonSerialize val skipId: String,
|
||||||
|
@JsonSerialize val episodeLength: Double
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
//fun String.getType(): String {
|
||||||
|
//
|
||||||
|
//
|
||||||
|
//
|
||||||
|
//
|
||||||
|
//
|
||||||
|
//
|
||||||
|
//
|
||||||
|
//
|
||||||
|
//}
|
||||||
|
|
||||||
|
data class AniSkipInterval(
|
||||||
|
@JsonSerialize val startTime: Double,
|
||||||
|
@JsonSerialize val endTime: Double
|
||||||
|
)
|
||||||
|
}
|
|
@ -0,0 +1,56 @@
|
||||||
|
package com.lagradost.cloudstream3.widget
|
||||||
|
|
||||||
|
import android.content.Context
|
||||||
|
import android.graphics.Matrix
|
||||||
|
import android.graphics.drawable.Drawable
|
||||||
|
import android.util.AttributeSet
|
||||||
|
|
||||||
|
// taken from https://gist.github.com/arriolac/3843346
|
||||||
|
class TopCropImageView : androidx.appcompat.widget.AppCompatImageView {
|
||||||
|
constructor(context: Context) : super(context) {
|
||||||
|
init()
|
||||||
|
}
|
||||||
|
|
||||||
|
constructor(context: Context, attrs: AttributeSet?) : super(context, attrs) {
|
||||||
|
init()
|
||||||
|
}
|
||||||
|
|
||||||
|
constructor(context: Context, attrs: AttributeSet?, defStyleAttr: Int) : super(
|
||||||
|
context,
|
||||||
|
attrs,
|
||||||
|
defStyleAttr
|
||||||
|
) {
|
||||||
|
init()
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onLayout(changed: Boolean, left: Int, top: Int, right: Int, bottom: Int) {
|
||||||
|
super.onLayout(changed, left, top, right, bottom)
|
||||||
|
recomputeImgMatrix()
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun setFrame(l: Int, t: Int, r: Int, b: Int): Boolean {
|
||||||
|
recomputeImgMatrix()
|
||||||
|
return super.setFrame(l, t, r, b)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun init() {
|
||||||
|
scaleType = ScaleType.MATRIX
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun recomputeImgMatrix() {
|
||||||
|
val drawable: Drawable = drawable ?: return
|
||||||
|
val matrix: Matrix = imageMatrix
|
||||||
|
val scale: Float
|
||||||
|
val viewWidth: Int = width - paddingLeft - paddingRight
|
||||||
|
val viewHeight: Int = height - paddingTop - paddingBottom
|
||||||
|
val drawableWidth: Int = drawable.intrinsicWidth
|
||||||
|
val drawableHeight: Int = drawable.intrinsicHeight
|
||||||
|
scale = if (drawableWidth * viewHeight > drawableHeight * viewWidth) {
|
||||||
|
viewHeight.toFloat() / drawableHeight.toFloat()
|
||||||
|
} else {
|
||||||
|
viewWidth.toFloat() / drawableWidth.toFloat()
|
||||||
|
}
|
||||||
|
matrix.setScale(scale, scale)
|
||||||
|
imageMatrix = matrix
|
||||||
|
}
|
||||||
|
}
|
|
@ -2,129 +2,129 @@
|
||||||
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||||
xmlns:tools="http://schemas.android.com/tools"
|
xmlns:tools="http://schemas.android.com/tools"
|
||||||
|
android:id="@+id/player_background"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="match_parent"
|
android:layout_height="match_parent"
|
||||||
android:orientation="horizontal"
|
|
||||||
android:id="@+id/player_background"
|
|
||||||
app:backgroundTint="@android:color/black"
|
|
||||||
android:background="@android:color/black"
|
android:background="@android:color/black"
|
||||||
|
android:orientation="horizontal"
|
||||||
android:screenOrientation="sensorLandscape"
|
android:screenOrientation="sensorLandscape"
|
||||||
|
app:backgroundTint="@android:color/black"
|
||||||
app:surface_type="texture_view">
|
app:surface_type="texture_view">
|
||||||
<!--
|
<!--
|
||||||
app:fastforward_increment="10000"
|
app:fastforward_increment="10000"
|
||||||
app:rewind_increment="10000"-->
|
app:rewind_increment="10000"-->
|
||||||
<com.google.android.exoplayer2.ui.PlayerView
|
<com.google.android.exoplayer2.ui.PlayerView
|
||||||
android:id="@+id/player_view"
|
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_width="match_parent"
|
||||||
android:layout_height="match_parent"
|
android:layout_height="match_parent"
|
||||||
app:backgroundTint="@android:color/black"
|
|
||||||
android:background="@android:color/black"
|
android:background="@android:color/black"
|
||||||
|
app:auto_show="true"
|
||||||
|
app:backgroundTint="@android:color/black"
|
||||||
|
app:controller_layout_id="@layout/player_custom_layout"
|
||||||
|
app:hide_on_touch="false"
|
||||||
app:layout_constraintBottom_toBottomOf="parent"
|
app:layout_constraintBottom_toBottomOf="parent"
|
||||||
app:layout_constraintEnd_toEndOf="parent"
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
app:layout_constraintStart_toStartOf="parent"
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
app:layout_constraintTop_toTopOf="parent"
|
app:layout_constraintTop_toTopOf="parent"
|
||||||
app:controller_layout_id="@layout/player_custom_layout" />
|
app:show_timeout="0" />
|
||||||
|
|
||||||
<FrameLayout
|
<FrameLayout
|
||||||
|
android:id="@+id/player_loading_overlay"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent"
|
||||||
|
android:background="@android:color/black"
|
||||||
|
android:backgroundTint="@android:color/black"
|
||||||
app:layout_constraintBottom_toBottomOf="parent"
|
app:layout_constraintBottom_toBottomOf="parent"
|
||||||
app:layout_constraintEnd_toEndOf="parent"
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
app:layout_constraintStart_toStartOf="parent"
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
app:layout_constraintTop_toTopOf="parent"
|
app:layout_constraintTop_toTopOf="parent">
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="match_parent"
|
|
||||||
android:id="@+id/player_loading_overlay"
|
|
||||||
android:background="@android:color/black"
|
|
||||||
android:backgroundTint="@android:color/black">
|
|
||||||
|
|
||||||
<com.google.android.material.button.MaterialButton
|
<com.google.android.material.button.MaterialButton
|
||||||
tools:visibility="visible"
|
|
||||||
android:visibility="gone"
|
|
||||||
android:layout_marginTop="70dp"
|
|
||||||
android:layout_gravity="center"
|
|
||||||
app:cornerRadius="4dp"
|
|
||||||
android:id="@+id/overlay_loading_skip_button"
|
android:id="@+id/overlay_loading_skip_button"
|
||||||
android:text="@string/skip_loading"
|
|
||||||
|
|
||||||
app:rippleColor="?attr/colorPrimary"
|
|
||||||
android:textColor="?attr/textColor"
|
|
||||||
app:iconTint="?attr/textColor"
|
|
||||||
android:textAllCaps="false"
|
|
||||||
app:icon="@drawable/ic_baseline_skip_next_24"
|
|
||||||
android:backgroundTint="@color/transparent"
|
|
||||||
style="@style/Widget.MaterialComponents.Button.OutlinedButton"
|
style="@style/Widget.MaterialComponents.Button.OutlinedButton"
|
||||||
android:layout_width="wrap_content"
|
android:layout_width="wrap_content"
|
||||||
android:layout_height="45dp" />
|
android:layout_height="45dp"
|
||||||
|
android:layout_gravity="center"
|
||||||
|
android:layout_marginTop="70dp"
|
||||||
|
android:backgroundTint="@color/transparent"
|
||||||
|
|
||||||
|
android:text="@string/skip_loading"
|
||||||
|
android:textAllCaps="false"
|
||||||
|
android:textColor="?attr/textColor"
|
||||||
|
android:visibility="gone"
|
||||||
|
app:cornerRadius="4dp"
|
||||||
|
app:icon="@drawable/ic_baseline_skip_next_24"
|
||||||
|
app:iconTint="?attr/textColor"
|
||||||
|
app:rippleColor="?attr/colorPrimary"
|
||||||
|
tools:visibility="visible" />
|
||||||
|
|
||||||
<ProgressBar
|
<ProgressBar
|
||||||
|
android:id="@+id/main_load"
|
||||||
android:layout_width="50dp"
|
android:layout_width="50dp"
|
||||||
android:layout_height="50dp"
|
android:layout_height="50dp"
|
||||||
android:layout_gravity="center"
|
android:layout_gravity="center" />
|
||||||
android:id="@+id/main_load" />
|
|
||||||
|
|
||||||
<FrameLayout
|
<FrameLayout
|
||||||
android:id="@+id/video_go_back_holder_holder"
|
android:id="@+id/video_go_back_holder_holder"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
android:layout_margin="5dp"
|
android:layout_margin="5dp"
|
||||||
app:layout_constraintStart_toStartOf="parent"
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
app:layout_constraintTop_toTopOf="parent"
|
app:layout_constraintTop_toTopOf="parent">
|
||||||
android:layout_width="wrap_content"
|
|
||||||
android:layout_height="wrap_content">
|
|
||||||
|
|
||||||
<ImageView
|
<ImageView
|
||||||
android:layout_width="30dp"
|
android:layout_width="30dp"
|
||||||
android:layout_height="30dp"
|
android:layout_height="30dp"
|
||||||
android:layout_gravity="center"
|
android:layout_gravity="center"
|
||||||
|
android:contentDescription="@string/go_back_img_des"
|
||||||
android:src="@drawable/ic_baseline_arrow_back_24"
|
android:src="@drawable/ic_baseline_arrow_back_24"
|
||||||
app:tint="@android:color/white"
|
app:tint="@android:color/white" />
|
||||||
android:contentDescription="@string/go_back_img_des" />
|
|
||||||
|
|
||||||
<ImageView
|
<ImageView
|
||||||
android:id="@+id/player_loading_go_back"
|
android:id="@+id/player_loading_go_back"
|
||||||
android:layout_width="70dp"
|
android:layout_width="70dp"
|
||||||
android:layout_height="70dp"
|
android:layout_height="70dp"
|
||||||
android:layout_gravity="center"
|
android:layout_gravity="center"
|
||||||
android:focusable="true"
|
|
||||||
android:clickable="true"
|
|
||||||
android:background="@drawable/video_tap_button_always_white"
|
android:background="@drawable/video_tap_button_always_white"
|
||||||
android:contentDescription="@string/go_back_img_des" />
|
android:clickable="true"
|
||||||
|
android:contentDescription="@string/go_back_img_des"
|
||||||
|
android:focusable="true" />
|
||||||
</FrameLayout>
|
</FrameLayout>
|
||||||
</FrameLayout>
|
</FrameLayout>
|
||||||
|
|
||||||
<FrameLayout
|
<FrameLayout
|
||||||
android:visibility="gone"
|
android:id="@+id/player_torrent_info"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
android:paddingStart="20dp"
|
android:paddingStart="20dp"
|
||||||
android:paddingEnd="20dp"
|
android:paddingEnd="20dp"
|
||||||
|
android:visibility="gone"
|
||||||
app:layout_constraintBottom_toBottomOf="parent"
|
app:layout_constraintBottom_toBottomOf="parent"
|
||||||
app:layout_constraintEnd_toEndOf="parent"
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
app:layout_constraintStart_toStartOf="parent"
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
app:layout_constraintTop_toTopOf="parent"
|
app:layout_constraintTop_toTopOf="parent">
|
||||||
android:id="@+id/player_torrent_info"
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="wrap_content">
|
|
||||||
|
|
||||||
<TextView
|
<TextView
|
||||||
|
android:id="@+id/video_torrent_progress"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
app:layout_constraintTop_toTopOf="parent"
|
|
||||||
app:layout_constraintLeft_toLeftOf="parent"
|
|
||||||
android:gravity="start"
|
|
||||||
android:layout_marginTop="15dp"
|
android:layout_marginTop="15dp"
|
||||||
android:textStyle="bold"
|
android:gravity="start"
|
||||||
android:textColor="@color/white"
|
android:textColor="@color/white"
|
||||||
android:id="@+id/video_torrent_progress"
|
android:textStyle="bold"
|
||||||
|
app:layout_constraintLeft_toLeftOf="parent"
|
||||||
|
app:layout_constraintTop_toTopOf="parent"
|
||||||
tools:text="78% at 18kb/s" />
|
tools:text="78% at 18kb/s" />
|
||||||
|
|
||||||
<TextView
|
<TextView
|
||||||
|
android:id="@+id/video_torrent_seeders"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
app:layout_constraintLeft_toLeftOf="parent"
|
|
||||||
android:gravity="start"
|
|
||||||
android:layout_marginTop="0dp"
|
android:layout_marginTop="0dp"
|
||||||
|
android:gravity="start"
|
||||||
android:textColor="@color/white"
|
android:textColor="@color/white"
|
||||||
android:id="@+id/video_torrent_seeders"
|
app:layout_constraintLeft_toLeftOf="parent"
|
||||||
tools:text="17 seeders"
|
app:layout_constraintTop_toBottomOf="@+id/player_video_title"
|
||||||
app:layout_constraintTop_toBottomOf="@+id/player_video_title" />
|
tools:text="17 seeders" />
|
||||||
</FrameLayout>
|
</FrameLayout>
|
||||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
</androidx.constraintlayout.widget.ConstraintLayout>
|
|
@ -318,6 +318,22 @@
|
||||||
</FrameLayout>
|
</FrameLayout>
|
||||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||||
|
|
||||||
|
<com.google.android.material.button.MaterialButton
|
||||||
|
android:id="@+id/skip_chapter_button"
|
||||||
|
style="@style/NiceButton"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginEnd="100dp"
|
||||||
|
android:backgroundTint="@color/skipOpTransparent"
|
||||||
|
android:padding="10dp"
|
||||||
|
android:textColor="@color/white"
|
||||||
|
app:cornerRadius="@dimen/rounded_button_radius"
|
||||||
|
app:layout_constraintBottom_toTopOf="@+id/bottom_player_bar"
|
||||||
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
|
app:strokeColor="@color/white"
|
||||||
|
app:strokeWidth="1dp"
|
||||||
|
android:text="Skip Opening" />
|
||||||
|
|
||||||
<LinearLayout
|
<LinearLayout
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
|
|
|
@ -41,6 +41,7 @@
|
||||||
|
|
||||||
<color name="black_overlay">#66000000</color>
|
<color name="black_overlay">#66000000</color>
|
||||||
<color name="darkBarTransparent">#C0121212</color>
|
<color name="darkBarTransparent">#C0121212</color>
|
||||||
|
<color name="skipOpTransparent">#C0121212</color>
|
||||||
<color name="darkBar">#121212</color>
|
<color name="darkBar">#121212</color>
|
||||||
<color name="videoProgress">#66B5B5B5</color> <!--66B5B5B5-->
|
<color name="videoProgress">#66B5B5B5</color> <!--66B5B5B5-->
|
||||||
<!--<color name="videoCache">#663D50FA</color>--> <!--66B5B5B5-->
|
<!--<color name="videoCache">#663D50FA</color>--> <!--66B5B5B5-->
|
||||||
|
|
|
@ -638,4 +638,13 @@
|
||||||
<string name="player_settings_play_in_browser">Browser</string>
|
<string name="player_settings_play_in_browser">Browser</string>
|
||||||
<string name="app_not_found_error">App not found</string>
|
<string name="app_not_found_error">App not found</string>
|
||||||
<string name="all_languages_preference">All Languages</string>
|
<string name="all_languages_preference">All Languages</string>
|
||||||
|
|
||||||
|
<string name="skip_type_format" formatted="true">Skip %s</string>
|
||||||
|
<string name="skip_type_op">Opening</string>
|
||||||
|
<string name="skip_type_ed">Ending</string>
|
||||||
|
<string name="skip_type_recap">Recap</string>
|
||||||
|
<string name="skip_type_mixed_ed">Mixed ending</string>
|
||||||
|
<string name="skip_type_mixed_op">Mixed opening</string>
|
||||||
|
<string name="skip_type_creddits">Credits</string>
|
||||||
|
<string name="skip_type_intro">Intro</string>
|
||||||
</resources>
|
</resources>
|
||||||
|
|
Loading…
Reference in a new issue