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()
|
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) {
|
||||||
|
|
|
@ -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))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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) {
|
||||||
|
|
|
@ -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?) {
|
||||||
|
|
|
@ -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()
|
||||||
|
|
|
@ -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()
|
||||||
|
|
|
@ -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()
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue