aniskip groundwork

This commit is contained in:
reduplicated 2022-11-04 12:46:30 +01:00
parent f84259f898
commit 7619b7e9d9
17 changed files with 409 additions and 121 deletions

View file

@ -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
} }

View file

@ -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 {

View file

@ -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

View 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) {

View file

@ -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")

View file

@ -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()

View file

@ -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 -> {

View file

@ -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,

View file

@ -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)
} }
} }
} }

View file

@ -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"

View file

@ -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)
} }
} }

View 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
)
}

View file

@ -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
}
}

View file

@ -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>

View file

@ -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"

View file

@ -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-->

View file

@ -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>