mirror of
https://github.com/recloudstream/cloudstream.git
synced 2024-08-15 01:53:11 +00:00
backend change for events in player
This commit is contained in:
parent
130cc16e25
commit
b6e99d7358
7 changed files with 289 additions and 186 deletions
|
@ -92,11 +92,11 @@ abstract class AbstractPlayerFragment(
|
|||
throw NotImplementedError()
|
||||
}
|
||||
|
||||
open fun playerPositionChanged(posDur: Pair<Long, Long>) {
|
||||
open fun playerPositionChanged(position: Long, duration : Long) {
|
||||
throw NotImplementedError()
|
||||
}
|
||||
|
||||
open fun playerDimensionsLoaded(widthHeight: Pair<Int, Int>) {
|
||||
open fun playerDimensionsLoaded(width: Int, height : Int) {
|
||||
throw NotImplementedError()
|
||||
}
|
||||
|
||||
|
@ -132,8 +132,8 @@ abstract class AbstractPlayerFragment(
|
|||
}
|
||||
}
|
||||
|
||||
private fun updateIsPlaying(playing: Pair<CSPlayerLoading, CSPlayerLoading>) {
|
||||
val (wasPlaying, isPlaying) = playing
|
||||
private fun updateIsPlaying(wasPlaying : CSPlayerLoading,
|
||||
isPlaying : CSPlayerLoading) {
|
||||
val isPlayingRightNow = CSPlayerLoading.IsPlaying == isPlaying
|
||||
val isPausedRightNow = CSPlayerLoading.IsPaused == isPlaying
|
||||
|
||||
|
@ -206,7 +206,7 @@ abstract class AbstractPlayerFragment(
|
|||
CSPlayerEvent.values()[intent.getIntExtra(
|
||||
EXTRA_CONTROL_TYPE,
|
||||
0
|
||||
)]
|
||||
)], source = PlayerEventSource.UI
|
||||
)
|
||||
}
|
||||
}
|
||||
|
@ -216,7 +216,7 @@ abstract class AbstractPlayerFragment(
|
|||
val isPlaying = player.getIsPlaying()
|
||||
val isPlayingValue =
|
||||
if (isPlaying) CSPlayerLoading.IsPlaying else CSPlayerLoading.IsPaused
|
||||
updateIsPlaying(Pair(isPlayingValue, isPlayingValue))
|
||||
updateIsPlaying(isPlayingValue, isPlayingValue)
|
||||
} else {
|
||||
// Restore the full-screen UI.
|
||||
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) {
|
||||
if (gotoNext && hasNextMirror()) {
|
||||
showToast(
|
||||
|
@ -326,6 +326,7 @@ abstract class AbstractPlayerFragment(
|
|||
}
|
||||
}
|
||||
|
||||
@SuppressLint("UnsafeOptInUsageError")
|
||||
private fun playerUpdated(player: Any?) {
|
||||
if (player is ExoPlayer) {
|
||||
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")
|
||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||
|
@ -374,25 +440,13 @@ abstract class AbstractPlayerFragment(
|
|||
|
||||
player.releaseCallbacks()
|
||||
player.initCallbacks(
|
||||
playerUpdated = ::playerUpdated,
|
||||
updateIsPlaying = ::updateIsPlaying,
|
||||
playerError = ::playerError,
|
||||
requestAutoFocus = ::requestAudioFocus,
|
||||
nextEpisode = ::nextEpisode,
|
||||
prevEpisode = ::prevEpisode,
|
||||
playerPositionChanged = ::playerPositionChanged,
|
||||
playerDimensionsLoaded = ::playerDimensionsLoaded,
|
||||
eventHandler = ::mainCallback,
|
||||
requestedListeningPercentages = listOf(
|
||||
SKIP_OP_VIDEO_PERCENTAGE,
|
||||
PRELOAD_NEXT_EPISODE_PERCENTAGE,
|
||||
NEXT_WATCH_EPISODE_PERCENTAGE,
|
||||
UPDATE_SYNC_PROGRESS_PERCENTAGE,
|
||||
),
|
||||
subtitlesUpdates = ::subtitlesChanged,
|
||||
embeddedSubtitlesFetched = ::embeddedSubtitlesFetched,
|
||||
onTracksInfoChanged = ::onTracksInfoChanged,
|
||||
onTimestampInvoked = ::onTimestamp,
|
||||
onTimestampSkipped = ::onTimestampSkipped
|
||||
)
|
||||
|
||||
if (player is CS3IPlayer) {
|
||||
|
@ -461,6 +515,7 @@ abstract class AbstractPlayerFragment(
|
|||
resize(PlayerResize.values()[resize], showToast)
|
||||
}
|
||||
|
||||
@SuppressLint("UnsafeOptInUsageError")
|
||||
fun resize(resize: PlayerResize, showToast: Boolean) {
|
||||
setKey(RESIZE_MODE_KEY, resize.ordinal)
|
||||
val type = when (resize) {
|
||||
|
|
|
@ -8,7 +8,6 @@ import android.os.Looper
|
|||
import android.util.Log
|
||||
import android.util.Rational
|
||||
import android.widget.FrameLayout
|
||||
import androidx.media3.common.C
|
||||
import androidx.media3.common.C.*
|
||||
import androidx.media3.common.Format
|
||||
import androidx.media3.common.MediaItem
|
||||
|
@ -135,80 +134,24 @@ class CS3IPlayer : IPlayer {
|
|||
* Boolean = if it's active
|
||||
* */
|
||||
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
|
||||
|
||||
/** Fired when seeking the player or on requestedListeningPercentages,
|
||||
* used to make things appear on que
|
||||
* position, duration */
|
||||
private var playerPositionChanged: ((Pair<Long, Long>) -> Unit)? = null
|
||||
private var eventHandler: ((PlayerEvent) -> Unit)? = null
|
||||
|
||||
private var nextEpisode: (() -> Unit)? = null
|
||||
private var prevEpisode: (() -> Unit)? = null
|
||||
|
||||
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
|
||||
fun event(event: PlayerEvent) {
|
||||
eventHandler?.invoke(event)
|
||||
}
|
||||
|
||||
override fun releaseCallbacks() {
|
||||
playerUpdated = 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
|
||||
eventHandler = null
|
||||
}
|
||||
|
||||
override fun initCallbacks(
|
||||
playerUpdated: (Any?) -> Unit,
|
||||
updateIsPlaying: ((Pair<CSPlayerLoading, CSPlayerLoading>) -> Unit)?,
|
||||
requestAutoFocus: (() -> Unit)?,
|
||||
playerError: ((Exception) -> Unit)?,
|
||||
playerDimensionsLoaded: ((Pair<Int, Int>) -> Unit)?,
|
||||
eventHandler: ((PlayerEvent) -> Unit),
|
||||
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.playerPositionChanged = playerPositionChanged
|
||||
this.nextEpisode = nextEpisode
|
||||
this.prevEpisode = prevEpisode
|
||||
this.subtitlesUpdates = subtitlesUpdates
|
||||
this.embeddedSubtitlesFetched = embeddedSubtitlesFetched
|
||||
this.onTracksInfoChanged = onTracksInfoChanged
|
||||
this.onTimestampInvoked = onTimestampInvoked
|
||||
this.onTimestampSkipped = onTimestampSkipped
|
||||
this.eventHandler = eventHandler
|
||||
}
|
||||
|
||||
// I know, this is not a perfect solution, however it works for fixing subs
|
||||
|
@ -217,7 +160,7 @@ class CS3IPlayer : IPlayer {
|
|||
try {
|
||||
Handler(it).post {
|
||||
try {
|
||||
seekTime(1L)
|
||||
seekTime(1L, source = PlayerEventSource.Player)
|
||||
} catch (e: Exception) {
|
||||
logError(e)
|
||||
}
|
||||
|
@ -271,8 +214,9 @@ class CS3IPlayer : IPlayer {
|
|||
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>? {
|
||||
if (id == null) return null
|
||||
// This beast of an expression does:
|
||||
|
@ -526,14 +470,14 @@ class CS3IPlayer : IPlayer {
|
|||
Log.i(TAG, "onStop")
|
||||
|
||||
saveData()
|
||||
exoPlayer?.pause()
|
||||
handleEvent(CSPlayerEvent.Pause, PlayerEventSource.Player)
|
||||
//releasePlayer()
|
||||
}
|
||||
|
||||
override fun onPause() {
|
||||
Log.i(TAG, "onPause")
|
||||
saveData()
|
||||
exoPlayer?.pause()
|
||||
handleEvent(CSPlayerEvent.Pause, PlayerEventSource.Player)
|
||||
//releasePlayer()
|
||||
}
|
||||
|
||||
|
@ -611,6 +555,7 @@ class CS3IPlayer : IPlayer {
|
|||
}
|
||||
}
|
||||
|
||||
@SuppressLint("UnsafeOptInUsageError")
|
||||
private fun Context.createOfflineSource(): DataSource.Factory {
|
||||
return DefaultDataSourceFactory(this, USER_AGENT)
|
||||
}
|
||||
|
@ -846,43 +791,55 @@ class CS3IPlayer : IPlayer {
|
|||
return null
|
||||
}
|
||||
|
||||
fun updatedTime(writePosition: Long? = null) {
|
||||
fun updatedTime(
|
||||
writePosition: Long? = null,
|
||||
source: PlayerEventSource = PlayerEventSource.Player
|
||||
) {
|
||||
val position = writePosition ?: exoPlayer?.currentPosition
|
||||
|
||||
getCurrentTimestamp(position)?.let { timestamp ->
|
||||
onTimestampInvoked?.invoke(timestamp)
|
||||
event(TimestampInvokedEvent(timestamp, source))
|
||||
}
|
||||
|
||||
val duration = exoPlayer?.contentDuration
|
||||
if (duration != null && position != null) {
|
||||
playerPositionChanged?.invoke(Pair(position, duration))
|
||||
event(
|
||||
PositionEvent(
|
||||
source,
|
||||
fromMs = exoPlayer?.currentPosition ?: 0,
|
||||
position,
|
||||
duration
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
override fun seekTime(time: Long) {
|
||||
exoPlayer?.seekTime(time)
|
||||
override fun seekTime(time: Long, source: PlayerEventSource) {
|
||||
exoPlayer?.seekTime(time, source)
|
||||
}
|
||||
|
||||
override fun seekTo(time: Long) {
|
||||
updatedTime(time)
|
||||
override fun seekTo(time: Long, source: PlayerEventSource) {
|
||||
updatedTime(time, source)
|
||||
exoPlayer?.seekTo(time)
|
||||
}
|
||||
|
||||
private fun ExoPlayer.seekTime(time: Long) {
|
||||
updatedTime(currentPosition + time)
|
||||
private fun ExoPlayer.seekTime(time: Long, source: PlayerEventSource) {
|
||||
updatedTime(currentPosition + time, source)
|
||||
seekTo(currentPosition + time)
|
||||
}
|
||||
|
||||
override fun handleEvent(event: CSPlayerEvent) {
|
||||
override fun handleEvent(event: CSPlayerEvent, source: PlayerEventSource) {
|
||||
Log.i(TAG, "handleEvent ${event.name}")
|
||||
try {
|
||||
exoPlayer?.apply {
|
||||
when (event) {
|
||||
CSPlayerEvent.Play -> {
|
||||
event(PlayEvent(source))
|
||||
play()
|
||||
}
|
||||
|
||||
CSPlayerEvent.Pause -> {
|
||||
event(PauseEvent(source))
|
||||
pause()
|
||||
}
|
||||
|
||||
|
@ -899,32 +856,32 @@ class CS3IPlayer : IPlayer {
|
|||
|
||||
CSPlayerEvent.PlayPauseToggle -> {
|
||||
if (isPlaying) {
|
||||
pause()
|
||||
handleEvent(CSPlayerEvent.Pause, source)
|
||||
} else {
|
||||
play()
|
||||
handleEvent(CSPlayerEvent.Play, source)
|
||||
}
|
||||
}
|
||||
|
||||
CSPlayerEvent.SeekForward -> seekTime(seekActionTime)
|
||||
CSPlayerEvent.SeekBack -> seekTime(-seekActionTime)
|
||||
CSPlayerEvent.NextEpisode -> nextEpisode?.invoke()
|
||||
CSPlayerEvent.PrevEpisode -> prevEpisode?.invoke()
|
||||
CSPlayerEvent.SeekForward -> seekTime(seekActionTime, source)
|
||||
CSPlayerEvent.SeekBack -> seekTime(-seekActionTime, source)
|
||||
CSPlayerEvent.NextEpisode -> event(EpisodeSeekEvent(offset = 1, source = source))
|
||||
CSPlayerEvent.PrevEpisode -> event(EpisodeSeekEvent(offset = -1, source = source))
|
||||
CSPlayerEvent.SkipCurrentChapter -> {
|
||||
//val dur = this@CS3IPlayer.getDuration() ?: return@apply
|
||||
getCurrentTimestamp()?.let { lastTimeStamp ->
|
||||
if (lastTimeStamp.skipToNextEpisode) {
|
||||
handleEvent(CSPlayerEvent.NextEpisode)
|
||||
handleEvent(CSPlayerEvent.NextEpisode, source)
|
||||
} else {
|
||||
seekTo(lastTimeStamp.endMs + 1L)
|
||||
}
|
||||
onTimestampSkipped?.invoke(lastTimeStamp)
|
||||
event(TimestampSkippedEvent(timestamp = lastTimeStamp, source = source))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
Log.e(TAG, "handleEvent error", e)
|
||||
playerError?.invoke(e)
|
||||
} catch (t: Throwable) {
|
||||
Log.e(TAG, "handleEvent error", t)
|
||||
event(ErrorEvent(t))
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -963,18 +920,14 @@ class CS3IPlayer : IPlayer {
|
|||
|
||||
requestSubtitleUpdate = ::reloadSubs
|
||||
|
||||
playerUpdated?.invoke(exoPlayer)
|
||||
event(PlayerAttachedEvent(exoPlayer))
|
||||
exoPlayer?.prepare()
|
||||
|
||||
exoPlayer?.let { exo ->
|
||||
updateIsPlaying?.invoke(
|
||||
Pair(
|
||||
CSPlayerLoading.IsBuffering,
|
||||
CSPlayerLoading.IsBuffering
|
||||
)
|
||||
)
|
||||
event(StatusEvent(CSPlayerLoading.IsBuffering, CSPlayerLoading.IsBuffering))
|
||||
isPlaying = exo.isPlaying
|
||||
}
|
||||
|
||||
exoPlayer?.addListener(object : Player.Listener {
|
||||
override fun onTracksChanged(tracks: Tracks) {
|
||||
normalSafeApiCall {
|
||||
|
@ -1008,18 +961,19 @@ class CS3IPlayer : IPlayer {
|
|||
)
|
||||
}
|
||||
|
||||
embeddedSubtitlesFetched?.invoke(exoPlayerReportedTracks)
|
||||
onTracksInfoChanged?.invoke()
|
||||
subtitlesUpdates?.invoke()
|
||||
event(EmbeddedSubtitlesFetchedEvent(tracks = exoPlayerReportedTracks))
|
||||
event(TracksChangedEvent())
|
||||
event(SubtitlesUpdatedEvent())
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressLint("UnsafeOptInUsageError")
|
||||
override fun onPlayerStateChanged(playWhenReady: Boolean, playbackState: Int) {
|
||||
exoPlayer?.let { exo ->
|
||||
updateIsPlaying?.invoke(
|
||||
Pair(
|
||||
if (isPlaying) CSPlayerLoading.IsPlaying else CSPlayerLoading.IsPaused,
|
||||
if (playbackState == Player.STATE_BUFFERING) CSPlayerLoading.IsBuffering else if (exo.isPlaying) CSPlayerLoading.IsPlaying else CSPlayerLoading.IsPaused
|
||||
event(
|
||||
StatusEvent(
|
||||
wasPlaying = if (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
|
||||
|
@ -1041,23 +995,15 @@ class CS3IPlayer : IPlayer {
|
|||
}
|
||||
|
||||
Player.STATE_ENDED -> {
|
||||
// Only play next episode if autoplay is on (default)
|
||||
if (PreferenceManager.getDefaultSharedPreferences(context)
|
||||
?.getBoolean(
|
||||
context.getString(com.lagradost.cloudstream3.R.string.autoplay_next_key),
|
||||
true
|
||||
) == true
|
||||
) {
|
||||
handleEvent(CSPlayerEvent.NextEpisode)
|
||||
}
|
||||
event(VideoEndedEvent())
|
||||
}
|
||||
|
||||
Player.STATE_BUFFERING -> {
|
||||
updatedTime()
|
||||
updatedTime(source = PlayerEventSource.Player)
|
||||
}
|
||||
|
||||
Player.STATE_IDLE -> {
|
||||
// IDLE
|
||||
|
||||
}
|
||||
|
||||
else -> Unit
|
||||
|
@ -1082,7 +1028,7 @@ class CS3IPlayer : IPlayer {
|
|||
}
|
||||
|
||||
else -> {
|
||||
playerError?.invoke(error)
|
||||
event(ErrorEvent(error))
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1096,7 +1042,7 @@ class CS3IPlayer : IPlayer {
|
|||
override fun onIsPlayingChanged(isPlaying: Boolean) {
|
||||
super.onIsPlayingChanged(isPlaying)
|
||||
if (isPlaying) {
|
||||
requestAutoFocus?.invoke()
|
||||
event(RequestAudioFocusEvent())
|
||||
onRenderFirst()
|
||||
}
|
||||
}
|
||||
|
@ -1116,12 +1062,15 @@ class CS3IPlayer : IPlayer {
|
|||
true
|
||||
) == true
|
||||
) {
|
||||
handleEvent(CSPlayerEvent.NextEpisode)
|
||||
handleEvent(
|
||||
CSPlayerEvent.NextEpisode,
|
||||
source = PlayerEventSource.Player
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
Player.STATE_BUFFERING -> {
|
||||
updatedTime()
|
||||
updatedTime(source = PlayerEventSource.Player)
|
||||
}
|
||||
|
||||
Player.STATE_IDLE -> {
|
||||
|
@ -1134,18 +1083,18 @@ class CS3IPlayer : IPlayer {
|
|||
|
||||
override fun onVideoSizeChanged(videoSize: VideoSize) {
|
||||
super.onVideoSizeChanged(videoSize)
|
||||
playerDimensionsLoaded?.invoke(Pair(videoSize.width, videoSize.height))
|
||||
event(ResizedEvent(height = videoSize.height, width = videoSize.width))
|
||||
}
|
||||
|
||||
override fun onRenderedFirstFrame() {
|
||||
super.onRenderedFirstFrame()
|
||||
onRenderFirst()
|
||||
updatedTime()
|
||||
updatedTime(source = PlayerEventSource.Player)
|
||||
}
|
||||
})
|
||||
} catch (e: Exception) {
|
||||
Log.e(TAG, "loadExo error", e)
|
||||
playerError?.invoke(e)
|
||||
} catch (t: Throwable) {
|
||||
Log.e(TAG, "loadExo error", t)
|
||||
event(ErrorEvent(t))
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1156,7 +1105,7 @@ class CS3IPlayer : IPlayer {
|
|||
lastTimeStamps = timeStamps
|
||||
timeStamps.forEach { timestamp ->
|
||||
exoPlayer?.createMessage { _, _ ->
|
||||
updatedTime()
|
||||
updatedTime(source = PlayerEventSource.Player)
|
||||
//if (payload is EpisodeSkip.SkipStamp) // this should always be true
|
||||
// onTimestampInvoked?.invoke(payload)
|
||||
}
|
||||
|
@ -1166,7 +1115,7 @@ class CS3IPlayer : IPlayer {
|
|||
?.setDeleteAfterDelivery(false)
|
||||
?.send()
|
||||
}
|
||||
updatedTime()
|
||||
updatedTime(source = PlayerEventSource.Player)
|
||||
}
|
||||
|
||||
@SuppressLint("UnsafeOptInUsageError")
|
||||
|
@ -1187,7 +1136,7 @@ class CS3IPlayer : IPlayer {
|
|||
|
||||
if (invalid) {
|
||||
releasePlayer(saveTime = false)
|
||||
playerError?.invoke(InvalidFileException("Too short playback"))
|
||||
event(ErrorEvent(InvalidFileException("Too short playback")))
|
||||
return
|
||||
}
|
||||
|
||||
|
@ -1196,7 +1145,7 @@ class CS3IPlayer : IPlayer {
|
|||
val width = format?.width
|
||||
val height = format?.height
|
||||
if (height != null && width != null) {
|
||||
playerDimensionsLoaded?.invoke(Pair(width, height))
|
||||
event(ResizedEvent(width = width, height = height))
|
||||
updatedTime()
|
||||
exoPlayer?.apply {
|
||||
requestedListeningPercentages?.forEach { percentage ->
|
||||
|
@ -1230,9 +1179,9 @@ class CS3IPlayer : IPlayer {
|
|||
|
||||
subtitleHelper.setActiveSubtitles(activeSubtitles.toSet())
|
||||
loadExo(context, listOf(MediaItemSlice(mediaItem, Long.MIN_VALUE)), subSources)
|
||||
} catch (e: Exception) {
|
||||
Log.e(TAG, "loadOfflinePlayer error", e)
|
||||
playerError?.invoke(e)
|
||||
} catch (t: Throwable) {
|
||||
Log.e(TAG, "loadOfflinePlayer error", t)
|
||||
event(ErrorEvent(t))
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1365,9 +1314,9 @@ class CS3IPlayer : IPlayer {
|
|||
}
|
||||
|
||||
loadExo(context, mediaItems, subSources, cacheFactory)
|
||||
} catch (e: Exception) {
|
||||
Log.e(TAG, "loadOnlinePlayer error", e)
|
||||
playerError?.invoke(e)
|
||||
} catch (t: Throwable) {
|
||||
Log.e(TAG, "loadOnlinePlayer error", t)
|
||||
event(ErrorEvent(t))
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -874,7 +874,7 @@ open class FullScreenPlayer : AbstractPlayerFragment() {
|
|||
currentTouch
|
||||
)?.let { seekTo ->
|
||||
if (abs(seekTo - startTime) > MINIMUM_SEEK_TIME) {
|
||||
player.seekTo(seekTo)
|
||||
player.seekTo(seekTo, PlayerEventSource.UI)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -909,7 +909,7 @@ open class FullScreenPlayer : AbstractPlayerFragment() {
|
|||
}
|
||||
|
||||
else -> {
|
||||
player.handleEvent(CSPlayerEvent.PlayPauseToggle)
|
||||
player.handleEvent(CSPlayerEvent.PlayPauseToggle, PlayerEventSource.UI)
|
||||
}
|
||||
}
|
||||
} else if (doubleTapEnabled && isFullScreenPlayer) {
|
||||
|
|
|
@ -551,7 +551,7 @@ class GeneratorPlayer : FullScreenPlayer() {
|
|||
//println("CURRENT SELECTED :$currentSelectedSubtitles of $currentSubs")
|
||||
context?.let { ctx ->
|
||||
val isPlaying = player.getIsPlaying()
|
||||
player.handleEvent(CSPlayerEvent.Pause)
|
||||
player.handleEvent(CSPlayerEvent.Pause, PlayerEventSource.UI)
|
||||
val currentSubtitles = sortSubs(currentSubs)
|
||||
|
||||
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")
|
||||
super.playerError(exception)
|
||||
}
|
||||
|
@ -945,14 +945,13 @@ class GeneratorPlayer : FullScreenPlayer() {
|
|||
|
||||
var maxEpisodeSet: Int? = null
|
||||
var hasRequestedStamps: Boolean = false
|
||||
override fun playerPositionChanged(posDur: Pair<Long, Long>) {
|
||||
override fun playerPositionChanged(position: Long, duration : Long) {
|
||||
// Don't save livestream data
|
||||
if ((currentMeta as? ResultEpisode)?.tvType?.isLiveStream() == true) return
|
||||
|
||||
// Don't save NSFW data
|
||||
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 (!hasRequestedStamps) {
|
||||
hasRequestedStamps = true
|
||||
|
@ -1209,8 +1208,8 @@ class GeneratorPlayer : FullScreenPlayer() {
|
|||
}
|
||||
}
|
||||
|
||||
override fun playerDimensionsLoaded(widthHeight: Pair<Int, Int>) {
|
||||
setPlayerDimen(widthHeight)
|
||||
override fun playerDimensionsLoaded(width: Int, height : Int) {
|
||||
setPlayerDimen(width to height)
|
||||
}
|
||||
|
||||
private fun unwrapBundle(savedInstanceState: Bundle?) {
|
||||
|
|
|
@ -45,9 +45,120 @@ enum class CSPlayerLoading {
|
|||
IsPaused,
|
||||
IsPlaying,
|
||||
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 {
|
||||
/**
|
||||
|
@ -108,27 +219,16 @@ interface IPlayer {
|
|||
fun getDuration(): Long?
|
||||
fun getPosition(): Long?
|
||||
|
||||
fun seekTime(time: Long)
|
||||
fun seekTo(time: Long)
|
||||
fun seekTime(time: Long, source: PlayerEventSource = PlayerEventSource.UI)
|
||||
fun seekTo(time: Long, source: PlayerEventSource = PlayerEventSource.UI)
|
||||
|
||||
fun getSubtitleOffset(): Long // in ms
|
||||
fun setSubtitleOffset(offset: Long) // in ms
|
||||
|
||||
fun initCallbacks(
|
||||
playerUpdated: (Any?) -> Unit, // attach player to view
|
||||
updateIsPlaying: ((Pair<CSPlayerLoading, CSPlayerLoading>) -> Unit)? = null, // (wasPlaying, isPlaying)
|
||||
requestAutoFocus: (() -> Unit)? = null, // current player starts, asking for all other programs to shut the fuck up
|
||||
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)
|
||||
eventHandler: ((PlayerEvent) -> Unit),
|
||||
/** this is used to request when the player should report back view percentage */
|
||||
requestedListeningPercentages: List<Int>? = null,
|
||||
)
|
||||
|
||||
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 getCurrentPreferredSubtitle(): SubtitleData?
|
||||
|
||||
fun handleEvent(event: CSPlayerEvent)
|
||||
fun handleEvent(event: CSPlayerEvent, source: PlayerEventSource = PlayerEventSource.UI)
|
||||
|
||||
fun onStop()
|
||||
fun onPause()
|
||||
|
|
|
@ -130,8 +130,8 @@ open class ResultFragmentPhone : FullScreenPlayer() {
|
|||
return currentTrailerIndex + 1 < currentTrailers.size
|
||||
}
|
||||
|
||||
override fun playerError(exception: Exception) {
|
||||
if (player.getIsPlaying()) { // because we dont want random toasts in player
|
||||
override fun playerError(exception: Throwable) {
|
||||
if (player.getIsPlaying()) { // because we don't want random toasts in player
|
||||
super.playerError(exception)
|
||||
} else {
|
||||
nextMirror()
|
||||
|
|
|
@ -3,7 +3,6 @@ package com.lagradost.cloudstream3.ui.result
|
|||
import android.animation.ValueAnimator
|
||||
import android.content.Context
|
||||
import android.content.res.Configuration
|
||||
import android.graphics.Rect
|
||||
import android.os.Bundle
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
|
@ -12,6 +11,7 @@ import androidx.core.view.isGone
|
|||
import androidx.core.view.isVisible
|
||||
import com.lagradost.cloudstream3.R
|
||||
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.utils.IOnBackPressed
|
||||
|
||||
|
@ -32,7 +32,7 @@ open class ResultTrailerPlayer : ResultFragmentPhone(), IOnBackPressed {
|
|||
|
||||
override fun prevEpisode() {}
|
||||
|
||||
override fun playerPositionChanged(posDur: Pair<Long, Long>) {}
|
||||
override fun playerPositionChanged(position: Long, duration : Long) {}
|
||||
|
||||
override fun nextMirror() {}
|
||||
|
||||
|
@ -99,8 +99,8 @@ open class ResultTrailerPlayer : ResultFragmentPhone(), IOnBackPressed {
|
|||
}
|
||||
}
|
||||
|
||||
override fun playerDimensionsLoaded(widthHeight: Pair<Int, Int>) {
|
||||
playerWidthHeight = widthHeight
|
||||
override fun playerDimensionsLoaded(width: Int, height : Int) {
|
||||
playerWidthHeight = width to height
|
||||
fixPlayerSize()
|
||||
}
|
||||
|
||||
|
@ -164,7 +164,7 @@ open class ResultTrailerPlayer : ResultFragmentPhone(), IOnBackPressed {
|
|||
|
||||
playerBinding?.playerIntroPlay?.setOnClickListener {
|
||||
playerBinding?.playerIntroPlay?.isGone = true
|
||||
player.handleEvent(CSPlayerEvent.Play)
|
||||
player.handleEvent(CSPlayerEvent.Play, PlayerEventSource.UI)
|
||||
updateUIVisibility()
|
||||
fixPlayerSize()
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue