backend change for events in player

This commit is contained in:
LagradOst 2023-09-09 23:18:21 +02:00
parent 130cc16e25
commit b6e99d7358
7 changed files with 289 additions and 186 deletions

View file

@ -92,11 +92,11 @@ abstract class AbstractPlayerFragment(
throw NotImplementedError() throw NotImplementedError()
} }
open fun playerPositionChanged(posDur: Pair<Long, Long>) { open fun playerPositionChanged(position: Long, duration : Long) {
throw NotImplementedError() throw NotImplementedError()
} }
open fun playerDimensionsLoaded(widthHeight: Pair<Int, Int>) { open fun playerDimensionsLoaded(width: Int, height : Int) {
throw NotImplementedError() throw NotImplementedError()
} }
@ -132,8 +132,8 @@ abstract class AbstractPlayerFragment(
} }
} }
private fun updateIsPlaying(playing: Pair<CSPlayerLoading, CSPlayerLoading>) { private fun updateIsPlaying(wasPlaying : CSPlayerLoading,
val (wasPlaying, isPlaying) = playing isPlaying : CSPlayerLoading) {
val isPlayingRightNow = CSPlayerLoading.IsPlaying == isPlaying val isPlayingRightNow = CSPlayerLoading.IsPlaying == isPlaying
val isPausedRightNow = CSPlayerLoading.IsPaused == isPlaying val isPausedRightNow = CSPlayerLoading.IsPaused == isPlaying
@ -206,7 +206,7 @@ abstract class AbstractPlayerFragment(
CSPlayerEvent.values()[intent.getIntExtra( CSPlayerEvent.values()[intent.getIntExtra(
EXTRA_CONTROL_TYPE, EXTRA_CONTROL_TYPE,
0 0
)] )], source = PlayerEventSource.UI
) )
} }
} }
@ -216,7 +216,7 @@ abstract class AbstractPlayerFragment(
val isPlaying = player.getIsPlaying() val isPlaying = player.getIsPlaying()
val isPlayingValue = val isPlayingValue =
if (isPlaying) CSPlayerLoading.IsPlaying else CSPlayerLoading.IsPaused if (isPlaying) CSPlayerLoading.IsPlaying else CSPlayerLoading.IsPaused
updateIsPlaying(Pair(isPlayingValue, isPlayingValue)) updateIsPlaying(isPlayingValue, isPlayingValue)
} else { } else {
// Restore the full-screen UI. // Restore the full-screen UI.
piphide?.isVisible = true piphide?.isVisible = true
@ -249,7 +249,7 @@ abstract class AbstractPlayerFragment(
} }
} }
open fun playerError(exception: Exception) { open fun playerError(exception: Throwable) {
fun showToast(message: String, gotoNext: Boolean = false) { fun showToast(message: String, gotoNext: Boolean = false) {
if (gotoNext && hasNextMirror()) { if (gotoNext && hasNextMirror()) {
showToast( showToast(
@ -326,6 +326,7 @@ abstract class AbstractPlayerFragment(
} }
} }
@SuppressLint("UnsafeOptInUsageError")
private fun playerUpdated(player: Any?) { private fun playerUpdated(player: Any?) {
if (player is ExoPlayer) { if (player is ExoPlayer) {
context?.let { ctx -> context?.let { ctx ->
@ -366,6 +367,71 @@ abstract class AbstractPlayerFragment(
// } // }
//} //}
/** This receives the events from the player, if you want to append functionality you do it here,
* do note that this only receives events for UI changes,
* and returning early WONT stop it from changing in eg the player time or pause status */
open fun mainCallback(event : PlayerEvent) {
when(event) {
is ResizedEvent -> {
playerDimensionsLoaded(event.width, event.height)
}
is PlayerAttachedEvent -> {
playerUpdated(event.player)
}
is SubtitlesUpdatedEvent -> {
subtitlesChanged()
}
is TimestampSkippedEvent -> {
onTimestampSkipped(event.timestamp)
}
is TimestampInvokedEvent -> {
onTimestamp(event.timestamp)
}
is TracksChangedEvent -> {
onTracksInfoChanged()
}
is EmbeddedSubtitlesFetchedEvent -> {
embeddedSubtitlesFetched(event.tracks)
}
is ErrorEvent -> {
playerError(event.error)
}
is RequestAudioFocusEvent -> {
requestAudioFocus()
}
is EpisodeSeekEvent -> {
when(event.offset) {
-1 -> prevEpisode()
1 -> nextEpisode()
else -> {}
}
}
is StatusEvent -> {
updateIsPlaying(wasPlaying = event.wasPlaying, isPlaying = event.isPlaying)
}
is PositionEvent -> {
playerPositionChanged(position = event.toMs, duration = event.durationMs)
}
is VideoEndedEvent -> {
context?.let { ctx ->
// Only play next episode if autoplay is on (default)
if (PreferenceManager.getDefaultSharedPreferences(ctx)
?.getBoolean(
ctx.getString(R.string.autoplay_next_key),
true
) == true
) {
player.handleEvent(
CSPlayerEvent.NextEpisode,
source = PlayerEventSource.Player
)
}
}
}
is PauseEvent -> Unit
is PlayEvent -> Unit
}
}
@SuppressLint("SetTextI18n") @SuppressLint("SetTextI18n")
override fun onViewCreated(view: View, savedInstanceState: Bundle?) { override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
@ -374,25 +440,13 @@ abstract class AbstractPlayerFragment(
player.releaseCallbacks() player.releaseCallbacks()
player.initCallbacks( player.initCallbacks(
playerUpdated = ::playerUpdated, eventHandler = ::mainCallback,
updateIsPlaying = ::updateIsPlaying,
playerError = ::playerError,
requestAutoFocus = ::requestAudioFocus,
nextEpisode = ::nextEpisode,
prevEpisode = ::prevEpisode,
playerPositionChanged = ::playerPositionChanged,
playerDimensionsLoaded = ::playerDimensionsLoaded,
requestedListeningPercentages = listOf( requestedListeningPercentages = listOf(
SKIP_OP_VIDEO_PERCENTAGE, SKIP_OP_VIDEO_PERCENTAGE,
PRELOAD_NEXT_EPISODE_PERCENTAGE, PRELOAD_NEXT_EPISODE_PERCENTAGE,
NEXT_WATCH_EPISODE_PERCENTAGE, NEXT_WATCH_EPISODE_PERCENTAGE,
UPDATE_SYNC_PROGRESS_PERCENTAGE, UPDATE_SYNC_PROGRESS_PERCENTAGE,
), ),
subtitlesUpdates = ::subtitlesChanged,
embeddedSubtitlesFetched = ::embeddedSubtitlesFetched,
onTracksInfoChanged = ::onTracksInfoChanged,
onTimestampInvoked = ::onTimestamp,
onTimestampSkipped = ::onTimestampSkipped
) )
if (player is CS3IPlayer) { if (player is CS3IPlayer) {
@ -461,6 +515,7 @@ abstract class AbstractPlayerFragment(
resize(PlayerResize.values()[resize], showToast) resize(PlayerResize.values()[resize], showToast)
} }
@SuppressLint("UnsafeOptInUsageError")
fun resize(resize: PlayerResize, showToast: Boolean) { fun resize(resize: PlayerResize, showToast: Boolean) {
setKey(RESIZE_MODE_KEY, resize.ordinal) setKey(RESIZE_MODE_KEY, resize.ordinal)
val type = when (resize) { val type = when (resize) {

View file

@ -8,7 +8,6 @@ import android.os.Looper
import android.util.Log import android.util.Log
import android.util.Rational import android.util.Rational
import android.widget.FrameLayout import android.widget.FrameLayout
import androidx.media3.common.C
import androidx.media3.common.C.* import androidx.media3.common.C.*
import androidx.media3.common.Format import androidx.media3.common.Format
import androidx.media3.common.MediaItem import androidx.media3.common.MediaItem
@ -135,80 +134,24 @@ class CS3IPlayer : IPlayer {
* Boolean = if it's active * Boolean = if it's active
* */ * */
private var playerSelectedSubtitleTracks = listOf<Pair<String, Boolean>>() private var playerSelectedSubtitleTracks = listOf<Pair<String, Boolean>>()
/** isPlaying */
private var updateIsPlaying: ((Pair<CSPlayerLoading, CSPlayerLoading>) -> Unit)? = null
private var requestAutoFocus: (() -> Unit)? = null
private var playerError: ((Exception) -> Unit)? = null
private var subtitlesUpdates: (() -> Unit)? = null
/** width x height */
private var playerDimensionsLoaded: ((Pair<Int, Int>) -> Unit)? = null
/** used for playerPositionChanged */
private var requestedListeningPercentages: List<Int>? = null private var requestedListeningPercentages: List<Int>? = null
/** Fired when seeking the player or on requestedListeningPercentages, private var eventHandler: ((PlayerEvent) -> Unit)? = null
* used to make things appear on que
* position, duration */
private var playerPositionChanged: ((Pair<Long, Long>) -> Unit)? = null
private var nextEpisode: (() -> Unit)? = null fun event(event: PlayerEvent) {
private var prevEpisode: (() -> Unit)? = null eventHandler?.invoke(event)
}
private var playerUpdated: ((Any?) -> Unit)? = null
private var embeddedSubtitlesFetched: ((List<SubtitleData>) -> Unit)? = null
private var onTracksInfoChanged: (() -> Unit)? = null
private var onTimestampInvoked: ((EpisodeSkip.SkipStamp?) -> Unit)? = null
private var onTimestampSkipped: ((EpisodeSkip.SkipStamp) -> Unit)? = null
override fun releaseCallbacks() { override fun releaseCallbacks() {
playerUpdated = null eventHandler = null
updateIsPlaying = null
requestAutoFocus = null
playerError = null
playerDimensionsLoaded = null
requestedListeningPercentages = null
playerPositionChanged = null
nextEpisode = null
prevEpisode = null
subtitlesUpdates = null
onTracksInfoChanged = null
onTimestampInvoked = null
requestSubtitleUpdate = null
onTimestampSkipped = null
} }
override fun initCallbacks( override fun initCallbacks(
playerUpdated: (Any?) -> Unit, eventHandler: ((PlayerEvent) -> Unit),
updateIsPlaying: ((Pair<CSPlayerLoading, CSPlayerLoading>) -> Unit)?,
requestAutoFocus: (() -> Unit)?,
playerError: ((Exception) -> Unit)?,
playerDimensionsLoaded: ((Pair<Int, Int>) -> Unit)?,
requestedListeningPercentages: List<Int>?, requestedListeningPercentages: List<Int>?,
playerPositionChanged: ((Pair<Long, Long>) -> Unit)?,
nextEpisode: (() -> Unit)?,
prevEpisode: (() -> Unit)?,
subtitlesUpdates: (() -> Unit)?,
embeddedSubtitlesFetched: ((List<SubtitleData>) -> Unit)?,
onTracksInfoChanged: (() -> Unit)?,
onTimestampInvoked: ((EpisodeSkip.SkipStamp?) -> Unit)?,
onTimestampSkipped: ((EpisodeSkip.SkipStamp) -> Unit)?,
) { ) {
this.playerUpdated = playerUpdated
this.updateIsPlaying = updateIsPlaying
this.requestAutoFocus = requestAutoFocus
this.playerError = playerError
this.playerDimensionsLoaded = playerDimensionsLoaded
this.requestedListeningPercentages = requestedListeningPercentages this.requestedListeningPercentages = requestedListeningPercentages
this.playerPositionChanged = playerPositionChanged this.eventHandler = eventHandler
this.nextEpisode = nextEpisode
this.prevEpisode = prevEpisode
this.subtitlesUpdates = subtitlesUpdates
this.embeddedSubtitlesFetched = embeddedSubtitlesFetched
this.onTracksInfoChanged = onTracksInfoChanged
this.onTimestampInvoked = onTimestampInvoked
this.onTimestampSkipped = onTimestampSkipped
} }
// 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
@ -217,7 +160,7 @@ class CS3IPlayer : IPlayer {
try { try {
Handler(it).post { Handler(it).post {
try { try {
seekTime(1L) seekTime(1L, source = PlayerEventSource.Player)
} catch (e: Exception) { } catch (e: Exception) {
logError(e) logError(e)
} }
@ -271,8 +214,9 @@ class CS3IPlayer : IPlayer {
subtitleHelper.setAllSubtitles(subtitles) subtitleHelper.setAllSubtitles(subtitles)
} }
var currentSubtitles: SubtitleData? = null private var currentSubtitles: SubtitleData? = null
@SuppressLint("UnsafeOptInUsageError")
private fun List<Tracks.Group>.getTrack(id: String?): Pair<TrackGroup, Int>? { private fun List<Tracks.Group>.getTrack(id: String?): Pair<TrackGroup, Int>? {
if (id == null) return null if (id == null) return null
// This beast of an expression does: // This beast of an expression does:
@ -526,14 +470,14 @@ class CS3IPlayer : IPlayer {
Log.i(TAG, "onStop") Log.i(TAG, "onStop")
saveData() saveData()
exoPlayer?.pause() handleEvent(CSPlayerEvent.Pause, PlayerEventSource.Player)
//releasePlayer() //releasePlayer()
} }
override fun onPause() { override fun onPause() {
Log.i(TAG, "onPause") Log.i(TAG, "onPause")
saveData() saveData()
exoPlayer?.pause() handleEvent(CSPlayerEvent.Pause, PlayerEventSource.Player)
//releasePlayer() //releasePlayer()
} }
@ -611,6 +555,7 @@ class CS3IPlayer : IPlayer {
} }
} }
@SuppressLint("UnsafeOptInUsageError")
private fun Context.createOfflineSource(): DataSource.Factory { private fun Context.createOfflineSource(): DataSource.Factory {
return DefaultDataSourceFactory(this, USER_AGENT) return DefaultDataSourceFactory(this, USER_AGENT)
} }
@ -846,43 +791,55 @@ class CS3IPlayer : IPlayer {
return null return null
} }
fun updatedTime(writePosition: Long? = null) { fun updatedTime(
writePosition: Long? = null,
source: PlayerEventSource = PlayerEventSource.Player
) {
val position = writePosition ?: exoPlayer?.currentPosition val position = writePosition ?: exoPlayer?.currentPosition
getCurrentTimestamp(position)?.let { timestamp -> getCurrentTimestamp(position)?.let { timestamp ->
onTimestampInvoked?.invoke(timestamp) event(TimestampInvokedEvent(timestamp, source))
} }
val duration = exoPlayer?.contentDuration val duration = exoPlayer?.contentDuration
if (duration != null && position != null) { if (duration != null && position != null) {
playerPositionChanged?.invoke(Pair(position, duration)) event(
PositionEvent(
source,
fromMs = exoPlayer?.currentPosition ?: 0,
position,
duration
)
)
} }
} }
override fun seekTime(time: Long) { override fun seekTime(time: Long, source: PlayerEventSource) {
exoPlayer?.seekTime(time) exoPlayer?.seekTime(time, source)
} }
override fun seekTo(time: Long) { override fun seekTo(time: Long, source: PlayerEventSource) {
updatedTime(time) updatedTime(time, source)
exoPlayer?.seekTo(time) exoPlayer?.seekTo(time)
} }
private fun ExoPlayer.seekTime(time: Long) { private fun ExoPlayer.seekTime(time: Long, source: PlayerEventSource) {
updatedTime(currentPosition + time) updatedTime(currentPosition + time, source)
seekTo(currentPosition + time) seekTo(currentPosition + time)
} }
override fun handleEvent(event: CSPlayerEvent) { override fun handleEvent(event: CSPlayerEvent, source: PlayerEventSource) {
Log.i(TAG, "handleEvent ${event.name}") Log.i(TAG, "handleEvent ${event.name}")
try { try {
exoPlayer?.apply { exoPlayer?.apply {
when (event) { when (event) {
CSPlayerEvent.Play -> { CSPlayerEvent.Play -> {
event(PlayEvent(source))
play() play()
} }
CSPlayerEvent.Pause -> { CSPlayerEvent.Pause -> {
event(PauseEvent(source))
pause() pause()
} }
@ -899,32 +856,32 @@ class CS3IPlayer : IPlayer {
CSPlayerEvent.PlayPauseToggle -> { CSPlayerEvent.PlayPauseToggle -> {
if (isPlaying) { if (isPlaying) {
pause() handleEvent(CSPlayerEvent.Pause, source)
} else { } else {
play() handleEvent(CSPlayerEvent.Play, source)
} }
} }
CSPlayerEvent.SeekForward -> seekTime(seekActionTime) CSPlayerEvent.SeekForward -> seekTime(seekActionTime, source)
CSPlayerEvent.SeekBack -> seekTime(-seekActionTime) CSPlayerEvent.SeekBack -> seekTime(-seekActionTime, source)
CSPlayerEvent.NextEpisode -> nextEpisode?.invoke() CSPlayerEvent.NextEpisode -> event(EpisodeSeekEvent(offset = 1, source = source))
CSPlayerEvent.PrevEpisode -> prevEpisode?.invoke() CSPlayerEvent.PrevEpisode -> event(EpisodeSeekEvent(offset = -1, source = source))
CSPlayerEvent.SkipCurrentChapter -> { CSPlayerEvent.SkipCurrentChapter -> {
//val dur = this@CS3IPlayer.getDuration() ?: return@apply //val dur = this@CS3IPlayer.getDuration() ?: return@apply
getCurrentTimestamp()?.let { lastTimeStamp -> getCurrentTimestamp()?.let { lastTimeStamp ->
if (lastTimeStamp.skipToNextEpisode) { if (lastTimeStamp.skipToNextEpisode) {
handleEvent(CSPlayerEvent.NextEpisode) handleEvent(CSPlayerEvent.NextEpisode, source)
} else { } else {
seekTo(lastTimeStamp.endMs + 1L) seekTo(lastTimeStamp.endMs + 1L)
} }
onTimestampSkipped?.invoke(lastTimeStamp) event(TimestampSkippedEvent(timestamp = lastTimeStamp, source = source))
} }
} }
} }
} }
} catch (e: Exception) { } catch (t: Throwable) {
Log.e(TAG, "handleEvent error", e) Log.e(TAG, "handleEvent error", t)
playerError?.invoke(e) event(ErrorEvent(t))
} }
} }
@ -963,18 +920,14 @@ class CS3IPlayer : IPlayer {
requestSubtitleUpdate = ::reloadSubs requestSubtitleUpdate = ::reloadSubs
playerUpdated?.invoke(exoPlayer) event(PlayerAttachedEvent(exoPlayer))
exoPlayer?.prepare() exoPlayer?.prepare()
exoPlayer?.let { exo -> exoPlayer?.let { exo ->
updateIsPlaying?.invoke( event(StatusEvent(CSPlayerLoading.IsBuffering, CSPlayerLoading.IsBuffering))
Pair(
CSPlayerLoading.IsBuffering,
CSPlayerLoading.IsBuffering
)
)
isPlaying = exo.isPlaying isPlaying = exo.isPlaying
} }
exoPlayer?.addListener(object : Player.Listener { exoPlayer?.addListener(object : Player.Listener {
override fun onTracksChanged(tracks: Tracks) { override fun onTracksChanged(tracks: Tracks) {
normalSafeApiCall { normalSafeApiCall {
@ -1008,18 +961,19 @@ class CS3IPlayer : IPlayer {
) )
} }
embeddedSubtitlesFetched?.invoke(exoPlayerReportedTracks) event(EmbeddedSubtitlesFetchedEvent(tracks = exoPlayerReportedTracks))
onTracksInfoChanged?.invoke() event(TracksChangedEvent())
subtitlesUpdates?.invoke() event(SubtitlesUpdatedEvent())
} }
} }
@SuppressLint("UnsafeOptInUsageError")
override fun onPlayerStateChanged(playWhenReady: Boolean, playbackState: Int) { override fun onPlayerStateChanged(playWhenReady: Boolean, playbackState: Int) {
exoPlayer?.let { exo -> exoPlayer?.let { exo ->
updateIsPlaying?.invoke( event(
Pair( StatusEvent(
if (isPlaying) CSPlayerLoading.IsPlaying else CSPlayerLoading.IsPaused, wasPlaying = if (isPlaying) CSPlayerLoading.IsPlaying else CSPlayerLoading.IsPaused,
if (playbackState == Player.STATE_BUFFERING) CSPlayerLoading.IsBuffering else if (exo.isPlaying) CSPlayerLoading.IsPlaying else CSPlayerLoading.IsPaused isPlaying = if (playbackState == Player.STATE_BUFFERING) CSPlayerLoading.IsBuffering else if (exo.isPlaying) CSPlayerLoading.IsPlaying else CSPlayerLoading.IsPaused
) )
) )
isPlaying = exo.isPlaying isPlaying = exo.isPlaying
@ -1041,23 +995,15 @@ class CS3IPlayer : IPlayer {
} }
Player.STATE_ENDED -> { Player.STATE_ENDED -> {
// Only play next episode if autoplay is on (default) event(VideoEndedEvent())
if (PreferenceManager.getDefaultSharedPreferences(context)
?.getBoolean(
context.getString(com.lagradost.cloudstream3.R.string.autoplay_next_key),
true
) == true
) {
handleEvent(CSPlayerEvent.NextEpisode)
}
} }
Player.STATE_BUFFERING -> { Player.STATE_BUFFERING -> {
updatedTime() updatedTime(source = PlayerEventSource.Player)
} }
Player.STATE_IDLE -> { Player.STATE_IDLE -> {
// IDLE
} }
else -> Unit else -> Unit
@ -1082,7 +1028,7 @@ class CS3IPlayer : IPlayer {
} }
else -> { else -> {
playerError?.invoke(error) event(ErrorEvent(error))
} }
} }
@ -1096,7 +1042,7 @@ class CS3IPlayer : IPlayer {
override fun onIsPlayingChanged(isPlaying: Boolean) { override fun onIsPlayingChanged(isPlaying: Boolean) {
super.onIsPlayingChanged(isPlaying) super.onIsPlayingChanged(isPlaying)
if (isPlaying) { if (isPlaying) {
requestAutoFocus?.invoke() event(RequestAudioFocusEvent())
onRenderFirst() onRenderFirst()
} }
} }
@ -1116,12 +1062,15 @@ class CS3IPlayer : IPlayer {
true true
) == true ) == true
) { ) {
handleEvent(CSPlayerEvent.NextEpisode) handleEvent(
CSPlayerEvent.NextEpisode,
source = PlayerEventSource.Player
)
} }
} }
Player.STATE_BUFFERING -> { Player.STATE_BUFFERING -> {
updatedTime() updatedTime(source = PlayerEventSource.Player)
} }
Player.STATE_IDLE -> { Player.STATE_IDLE -> {
@ -1134,18 +1083,18 @@ class CS3IPlayer : IPlayer {
override fun onVideoSizeChanged(videoSize: VideoSize) { override fun onVideoSizeChanged(videoSize: VideoSize) {
super.onVideoSizeChanged(videoSize) super.onVideoSizeChanged(videoSize)
playerDimensionsLoaded?.invoke(Pair(videoSize.width, videoSize.height)) event(ResizedEvent(height = videoSize.height, width = videoSize.width))
} }
override fun onRenderedFirstFrame() { override fun onRenderedFirstFrame() {
super.onRenderedFirstFrame() super.onRenderedFirstFrame()
onRenderFirst() onRenderFirst()
updatedTime() updatedTime(source = PlayerEventSource.Player)
} }
}) })
} catch (e: Exception) { } catch (t: Throwable) {
Log.e(TAG, "loadExo error", e) Log.e(TAG, "loadExo error", t)
playerError?.invoke(e) event(ErrorEvent(t))
} }
} }
@ -1156,7 +1105,7 @@ class CS3IPlayer : IPlayer {
lastTimeStamps = timeStamps lastTimeStamps = timeStamps
timeStamps.forEach { timestamp -> timeStamps.forEach { timestamp ->
exoPlayer?.createMessage { _, _ -> exoPlayer?.createMessage { _, _ ->
updatedTime() updatedTime(source = PlayerEventSource.Player)
//if (payload is EpisodeSkip.SkipStamp) // this should always be true //if (payload is EpisodeSkip.SkipStamp) // this should always be true
// onTimestampInvoked?.invoke(payload) // onTimestampInvoked?.invoke(payload)
} }
@ -1166,7 +1115,7 @@ class CS3IPlayer : IPlayer {
?.setDeleteAfterDelivery(false) ?.setDeleteAfterDelivery(false)
?.send() ?.send()
} }
updatedTime() updatedTime(source = PlayerEventSource.Player)
} }
@SuppressLint("UnsafeOptInUsageError") @SuppressLint("UnsafeOptInUsageError")
@ -1187,7 +1136,7 @@ class CS3IPlayer : IPlayer {
if (invalid) { if (invalid) {
releasePlayer(saveTime = false) releasePlayer(saveTime = false)
playerError?.invoke(InvalidFileException("Too short playback")) event(ErrorEvent(InvalidFileException("Too short playback")))
return return
} }
@ -1196,7 +1145,7 @@ class CS3IPlayer : IPlayer {
val width = format?.width val width = format?.width
val height = format?.height val height = format?.height
if (height != null && width != null) { if (height != null && width != null) {
playerDimensionsLoaded?.invoke(Pair(width, height)) event(ResizedEvent(width = width, height = height))
updatedTime() updatedTime()
exoPlayer?.apply { exoPlayer?.apply {
requestedListeningPercentages?.forEach { percentage -> requestedListeningPercentages?.forEach { percentage ->
@ -1230,9 +1179,9 @@ class CS3IPlayer : IPlayer {
subtitleHelper.setActiveSubtitles(activeSubtitles.toSet()) subtitleHelper.setActiveSubtitles(activeSubtitles.toSet())
loadExo(context, listOf(MediaItemSlice(mediaItem, Long.MIN_VALUE)), subSources) loadExo(context, listOf(MediaItemSlice(mediaItem, Long.MIN_VALUE)), subSources)
} catch (e: Exception) { } catch (t: Throwable) {
Log.e(TAG, "loadOfflinePlayer error", e) Log.e(TAG, "loadOfflinePlayer error", t)
playerError?.invoke(e) event(ErrorEvent(t))
} }
} }
@ -1365,9 +1314,9 @@ class CS3IPlayer : IPlayer {
} }
loadExo(context, mediaItems, subSources, cacheFactory) loadExo(context, mediaItems, subSources, cacheFactory)
} catch (e: Exception) { } catch (t: Throwable) {
Log.e(TAG, "loadOnlinePlayer error", e) Log.e(TAG, "loadOnlinePlayer error", t)
playerError?.invoke(e) event(ErrorEvent(t))
} }
} }

View file

@ -874,7 +874,7 @@ open class FullScreenPlayer : AbstractPlayerFragment() {
currentTouch currentTouch
)?.let { seekTo -> )?.let { seekTo ->
if (abs(seekTo - startTime) > MINIMUM_SEEK_TIME) { if (abs(seekTo - startTime) > MINIMUM_SEEK_TIME) {
player.seekTo(seekTo) player.seekTo(seekTo, PlayerEventSource.UI)
} }
} }
} }
@ -909,7 +909,7 @@ open class FullScreenPlayer : AbstractPlayerFragment() {
} }
else -> { else -> {
player.handleEvent(CSPlayerEvent.PlayPauseToggle) player.handleEvent(CSPlayerEvent.PlayPauseToggle, PlayerEventSource.UI)
} }
} }
} else if (doubleTapEnabled && isFullScreenPlayer) { } else if (doubleTapEnabled && isFullScreenPlayer) {

View file

@ -551,7 +551,7 @@ class GeneratorPlayer : FullScreenPlayer() {
//println("CURRENT SELECTED :$currentSelectedSubtitles of $currentSubs") //println("CURRENT SELECTED :$currentSelectedSubtitles of $currentSubs")
context?.let { ctx -> context?.let { ctx ->
val isPlaying = player.getIsPlaying() val isPlaying = player.getIsPlaying()
player.handleEvent(CSPlayerEvent.Pause) player.handleEvent(CSPlayerEvent.Pause, PlayerEventSource.UI)
val currentSubtitles = sortSubs(currentSubs) val currentSubtitles = sortSubs(currentSubs)
val sourceDialog = Dialog(ctx, R.style.AlertDialogCustomBlack) val sourceDialog = Dialog(ctx, R.style.AlertDialogCustomBlack)
@ -883,7 +883,7 @@ class GeneratorPlayer : FullScreenPlayer() {
} }
override fun playerError(exception: Exception) { override fun playerError(exception: Throwable) {
Log.i(TAG, "playerError = $currentSelectedLink") Log.i(TAG, "playerError = $currentSelectedLink")
super.playerError(exception) super.playerError(exception)
} }
@ -945,14 +945,13 @@ class GeneratorPlayer : FullScreenPlayer() {
var maxEpisodeSet: Int? = null var maxEpisodeSet: Int? = null
var hasRequestedStamps: Boolean = false var hasRequestedStamps: Boolean = false
override fun playerPositionChanged(posDur: Pair<Long, Long>) { override fun playerPositionChanged(position: Long, duration : 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
// Don't save NSFW data // Don't save NSFW data
if ((currentMeta as? ResultEpisode)?.tvType == TvType.NSFW) return if ((currentMeta as? ResultEpisode)?.tvType == TvType.NSFW) return
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) { if (!hasRequestedStamps) {
hasRequestedStamps = true hasRequestedStamps = true
@ -1209,8 +1208,8 @@ class GeneratorPlayer : FullScreenPlayer() {
} }
} }
override fun playerDimensionsLoaded(widthHeight: Pair<Int, Int>) { override fun playerDimensionsLoaded(width: Int, height : Int) {
setPlayerDimen(widthHeight) setPlayerDimen(width to height)
} }
private fun unwrapBundle(savedInstanceState: Bundle?) { private fun unwrapBundle(savedInstanceState: Bundle?) {

View file

@ -45,9 +45,120 @@ enum class CSPlayerLoading {
IsPaused, IsPaused,
IsPlaying, IsPlaying,
IsBuffering, IsBuffering,
//IsDone,
} }
enum class PlayerEventSource {
/** This event was invoked from the user pressing some button or selecting something */
UI,
/** This event was invoked automatically */
Player,
/** This event was invoked from a external sync tool like WatchTogether */
Sync,
}
abstract class PlayerEvent {
abstract val source: PlayerEventSource
}
/** this is used to update UI based of the current time,
* using requestedListeningPercentages as well as saving time */
data class PositionEvent(
override val source: PlayerEventSource,
val fromMs: Long,
val toMs: Long,
/** duration of the entire video */
val durationMs: Long,
) : PlayerEvent() {
/** how many ms (+-) we have skipped */
val seekMs : Long get() = toMs - fromMs
}
/** player error when rendering or misc, used to display toast or log */
data class ErrorEvent(
val error: Throwable,
override val source: PlayerEventSource = PlayerEventSource.Player,
) : PlayerEvent()
/** Event when timestamps appear, null when it should disappear */
data class TimestampInvokedEvent(
val timestamp: EpisodeSkip.SkipStamp,
override val source: PlayerEventSource = PlayerEventSource.Player,
) : PlayerEvent()
/** Event for when a chapter is skipped, aka when event is handled (or for future use when skip automatically ads/sponsor) */
data class TimestampSkippedEvent(
val timestamp: EpisodeSkip.SkipStamp,
override val source: PlayerEventSource = PlayerEventSource.Player,
) : PlayerEvent()
/** this is used by the player to load the next or prev episode */
data class EpisodeSeekEvent(
/** -1 = prev, 1 = next, will never be 0, atm the user cant seek more than +-1 */
val offset: Int,
override val source: PlayerEventSource = PlayerEventSource.Player,
) : PlayerEvent() {
init {
assert(offset != 0)
}
}
/** Event when the video is resized aka changed resolution or mirror */
data class ResizedEvent(
val height: Int,
val width: Int,
override val source: PlayerEventSource = PlayerEventSource.Player,
) : PlayerEvent()
/** Event when the player status update, along with the previous status (for animation)*/
data class StatusEvent(
val wasPlaying: CSPlayerLoading,
val isPlaying: CSPlayerLoading,
override val source: PlayerEventSource = PlayerEventSource.Player
) : PlayerEvent()
/** Event when tracks are changed, used for UI changes */
data class TracksChangedEvent(
override val source: PlayerEventSource = PlayerEventSource.Player
) : PlayerEvent()
/** Event from player to give all embedded subtitles */
data class EmbeddedSubtitlesFetchedEvent(
val tracks: List<SubtitleData>,
override val source: PlayerEventSource = PlayerEventSource.Player
) : PlayerEvent()
/** on attach player to view */
data class PlayerAttachedEvent(
val player: Any?,
override val source: PlayerEventSource = PlayerEventSource.Player
) : PlayerEvent()
/** Event from player to inform that subtitles have updated in some way */
data class SubtitlesUpdatedEvent(
override val source: PlayerEventSource = PlayerEventSource.Player
) : PlayerEvent()
/** current player starts, asking for all other programs to shut the fuck up */
data class RequestAudioFocusEvent(
override val source: PlayerEventSource = PlayerEventSource.Player
) : PlayerEvent()
/** Pause event, separate from StatusEvent */
data class PauseEvent(
override val source: PlayerEventSource = PlayerEventSource.Player
) : PlayerEvent()
/** Play event, separate from StatusEvent */
data class PlayEvent(
override val source: PlayerEventSource = PlayerEventSource.Player
) : PlayerEvent()
/** Event when the player video has ended, up to the settings on what to do when that happens */
data class VideoEndedEvent(
override val source: PlayerEventSource = PlayerEventSource.Player
) : PlayerEvent()
interface Track { interface Track {
/** /**
@ -108,27 +219,16 @@ interface IPlayer {
fun getDuration(): Long? fun getDuration(): Long?
fun getPosition(): Long? fun getPosition(): Long?
fun seekTime(time: Long) fun seekTime(time: Long, source: PlayerEventSource = PlayerEventSource.UI)
fun seekTo(time: Long) fun seekTo(time: Long, source: PlayerEventSource = PlayerEventSource.UI)
fun getSubtitleOffset(): Long // in ms fun getSubtitleOffset(): Long // in ms
fun setSubtitleOffset(offset: Long) // in ms fun setSubtitleOffset(offset: Long) // in ms
fun initCallbacks( fun initCallbacks(
playerUpdated: (Any?) -> Unit, // attach player to view eventHandler: ((PlayerEvent) -> Unit),
updateIsPlaying: ((Pair<CSPlayerLoading, CSPlayerLoading>) -> Unit)? = null, // (wasPlaying, isPlaying) /** this is used to request when the player should report back view percentage */
requestAutoFocus: (() -> Unit)? = null, // current player starts, asking for all other programs to shut the fuck up requestedListeningPercentages: List<Int>? = null,
playerError: ((Exception) -> Unit)? = null, // player error when rendering or misc, used to display toast or log
playerDimensionsLoaded: ((Pair<Int, Int>) -> Unit)? = null, // (with, height), for UI
requestedListeningPercentages: List<Int>? = null, // this is used to request when the player should report back view percentage
playerPositionChanged: ((Pair<Long, Long>) -> Unit)? = null,// (position, duration) this is used to update UI based of the current time
nextEpisode: (() -> Unit)? = null, // this is used by the player to load the next episode
prevEpisode: (() -> Unit)? = null, // this is used by the player to load the previous episode
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
onTracksInfoChanged: (() -> Unit)? = null, // Callback when tracks are changed, used for UI changes
onTimestampInvoked: ((EpisodeSkip.SkipStamp?) -> Unit)? = null, // Callback when timestamps appear, null when it should disappear
onTimestampSkipped: ((EpisodeSkip.SkipStamp) -> Unit)? = null, // callback for when a chapter is skipped, aka when event is handled (or for future use when skip automatically ads/sponsor)
) )
fun releaseCallbacks() fun releaseCallbacks()
@ -155,7 +255,7 @@ interface IPlayer {
fun setPreferredSubtitles(subtitle: SubtitleData?): Boolean // returns true if the player requires a reload, null for nothing fun setPreferredSubtitles(subtitle: SubtitleData?): Boolean // returns true if the player requires a reload, null for nothing
fun getCurrentPreferredSubtitle(): SubtitleData? fun getCurrentPreferredSubtitle(): SubtitleData?
fun handleEvent(event: CSPlayerEvent) fun handleEvent(event: CSPlayerEvent, source: PlayerEventSource = PlayerEventSource.UI)
fun onStop() fun onStop()
fun onPause() fun onPause()

View file

@ -130,8 +130,8 @@ open class ResultFragmentPhone : FullScreenPlayer() {
return currentTrailerIndex + 1 < currentTrailers.size return currentTrailerIndex + 1 < currentTrailers.size
} }
override fun playerError(exception: Exception) { override fun playerError(exception: Throwable) {
if (player.getIsPlaying()) { // because we dont want random toasts in player if (player.getIsPlaying()) { // because we don't want random toasts in player
super.playerError(exception) super.playerError(exception)
} else { } else {
nextMirror() nextMirror()

View file

@ -3,7 +3,6 @@ package com.lagradost.cloudstream3.ui.result
import android.animation.ValueAnimator import android.animation.ValueAnimator
import android.content.Context import android.content.Context
import android.content.res.Configuration import android.content.res.Configuration
import android.graphics.Rect
import android.os.Bundle import android.os.Bundle
import android.view.View import android.view.View
import android.view.ViewGroup import android.view.ViewGroup
@ -12,6 +11,7 @@ import androidx.core.view.isGone
import androidx.core.view.isVisible import androidx.core.view.isVisible
import com.lagradost.cloudstream3.R import com.lagradost.cloudstream3.R
import com.lagradost.cloudstream3.ui.player.CSPlayerEvent import com.lagradost.cloudstream3.ui.player.CSPlayerEvent
import com.lagradost.cloudstream3.ui.player.PlayerEventSource
import com.lagradost.cloudstream3.ui.player.SubtitleData import com.lagradost.cloudstream3.ui.player.SubtitleData
import com.lagradost.cloudstream3.utils.IOnBackPressed import com.lagradost.cloudstream3.utils.IOnBackPressed
@ -32,7 +32,7 @@ open class ResultTrailerPlayer : ResultFragmentPhone(), IOnBackPressed {
override fun prevEpisode() {} override fun prevEpisode() {}
override fun playerPositionChanged(posDur: Pair<Long, Long>) {} override fun playerPositionChanged(position: Long, duration : Long) {}
override fun nextMirror() {} override fun nextMirror() {}
@ -99,8 +99,8 @@ open class ResultTrailerPlayer : ResultFragmentPhone(), IOnBackPressed {
} }
} }
override fun playerDimensionsLoaded(widthHeight: Pair<Int, Int>) { override fun playerDimensionsLoaded(width: Int, height : Int) {
playerWidthHeight = widthHeight playerWidthHeight = width to height
fixPlayerSize() fixPlayerSize()
} }
@ -164,7 +164,7 @@ open class ResultTrailerPlayer : ResultFragmentPhone(), IOnBackPressed {
playerBinding?.playerIntroPlay?.setOnClickListener { playerBinding?.playerIntroPlay?.setOnClickListener {
playerBinding?.playerIntroPlay?.isGone = true playerBinding?.playerIntroPlay?.isGone = true
player.handleEvent(CSPlayerEvent.Play) player.handleEvent(CSPlayerEvent.Play, PlayerEventSource.UI)
updateUIVisibility() updateUIVisibility()
fixPlayerSize() fixPlayerSize()
} }