mirror of
https://github.com/JustinZeus/Wholphin.git
synced 2026-07-08 15:50:16 +02:00
Merge branch 'main' into fea/live-tv
This commit is contained in:
commit
e0840bef3f
4 changed files with 120 additions and 34 deletions
|
|
@ -37,6 +37,7 @@ import com.github.damontecres.wholphin.ui.nav.Destination
|
|||
import com.github.damontecres.wholphin.ui.nav.NavigationManager
|
||||
import com.github.damontecres.wholphin.ui.theme.WholphinTheme
|
||||
import com.github.damontecres.wholphin.util.AppUpgradeHandler
|
||||
import com.github.damontecres.wholphin.util.ServerEventListener
|
||||
import com.github.damontecres.wholphin.util.UpdateChecker
|
||||
import com.github.damontecres.wholphin.util.profile.createDeviceProfile
|
||||
import dagger.hilt.android.AndroidEntryPoint
|
||||
|
|
@ -66,6 +67,9 @@ class MainActivity : AppCompatActivity() {
|
|||
@Inject
|
||||
lateinit var appUpgradeHandler: AppUpgradeHandler
|
||||
|
||||
@Inject
|
||||
lateinit var serverEventListener: ServerEventListener
|
||||
|
||||
@OptIn(ExperimentalTvMaterial3Api::class)
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
|
|
@ -146,6 +150,9 @@ class MainActivity : AppCompatActivity() {
|
|||
}
|
||||
}
|
||||
}
|
||||
LaunchedEffect(server) {
|
||||
serverEventListener.init()
|
||||
}
|
||||
ApplicationContent(
|
||||
user = user,
|
||||
server = server,
|
||||
|
|
|
|||
|
|
@ -32,6 +32,8 @@ import com.github.damontecres.wholphin.preferences.SkipSegmentBehavior
|
|||
import com.github.damontecres.wholphin.preferences.UserPreferences
|
||||
import com.github.damontecres.wholphin.ui.nav.Destination
|
||||
import com.github.damontecres.wholphin.ui.nav.NavigationManager
|
||||
import com.github.damontecres.wholphin.ui.seekBack
|
||||
import com.github.damontecres.wholphin.ui.seekForward
|
||||
import com.github.damontecres.wholphin.ui.setValueOnMain
|
||||
import com.github.damontecres.wholphin.ui.showToast
|
||||
import com.github.damontecres.wholphin.ui.toServerString
|
||||
|
|
@ -51,6 +53,8 @@ import dagger.hilt.android.qualifiers.ApplicationContext
|
|||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.Job
|
||||
import kotlinx.coroutines.delay
|
||||
import kotlinx.coroutines.flow.launchIn
|
||||
import kotlinx.coroutines.flow.onEach
|
||||
import kotlinx.coroutines.isActive
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.withContext
|
||||
|
|
@ -60,6 +64,7 @@ import org.jellyfin.sdk.api.client.extensions.mediaSegmentsApi
|
|||
import org.jellyfin.sdk.api.client.extensions.trickplayApi
|
||||
import org.jellyfin.sdk.api.client.extensions.userLibraryApi
|
||||
import org.jellyfin.sdk.api.client.extensions.videosApi
|
||||
import org.jellyfin.sdk.api.sockets.subscribe
|
||||
import org.jellyfin.sdk.model.api.BaseItemDto
|
||||
import org.jellyfin.sdk.model.api.BaseItemKind
|
||||
import org.jellyfin.sdk.model.api.DeviceProfile
|
||||
|
|
@ -69,6 +74,8 @@ import org.jellyfin.sdk.model.api.MediaSourceInfo
|
|||
import org.jellyfin.sdk.model.api.MediaStreamType
|
||||
import org.jellyfin.sdk.model.api.PlayMethod
|
||||
import org.jellyfin.sdk.model.api.PlaybackInfoDto
|
||||
import org.jellyfin.sdk.model.api.PlaystateCommand
|
||||
import org.jellyfin.sdk.model.api.PlaystateMessage
|
||||
import org.jellyfin.sdk.model.api.SubtitlePlaybackMode
|
||||
import org.jellyfin.sdk.model.api.TrickplayInfo
|
||||
import org.jellyfin.sdk.model.extensions.inWholeTicks
|
||||
|
|
@ -148,6 +155,7 @@ class PlaybackViewModel
|
|||
}
|
||||
}
|
||||
addCloseable { player.release() }
|
||||
subscribe()
|
||||
}
|
||||
|
||||
fun init(
|
||||
|
|
@ -741,6 +749,38 @@ class PlaybackViewModel
|
|||
activityListener?.release()
|
||||
player.release()
|
||||
}
|
||||
|
||||
fun subscribe() {
|
||||
api.webSocket
|
||||
.subscribe<PlaystateMessage>()
|
||||
.onEach { message ->
|
||||
message.data?.let {
|
||||
when (it.command) {
|
||||
PlaystateCommand.STOP -> {
|
||||
release()
|
||||
navigationManager.goBack()
|
||||
}
|
||||
|
||||
PlaystateCommand.PAUSE -> player.pause()
|
||||
PlaystateCommand.UNPAUSE -> player.play()
|
||||
PlaystateCommand.NEXT_TRACK -> playUpNextUp()
|
||||
PlaystateCommand.PREVIOUS_TRACK -> playPrevious()
|
||||
PlaystateCommand.SEEK -> it.seekPositionTicks?.ticks?.let { player.seekTo(it.inWholeMilliseconds) }
|
||||
PlaystateCommand.REWIND ->
|
||||
player.seekBack(
|
||||
preferences.appPreferences.playbackPreferences.skipBackMs.milliseconds,
|
||||
)
|
||||
|
||||
PlaystateCommand.FAST_FORWARD ->
|
||||
player.seekForward(
|
||||
preferences.appPreferences.playbackPreferences.skipForwardMs.milliseconds,
|
||||
)
|
||||
|
||||
PlaystateCommand.PLAY_PAUSE -> if (player.isPlaying) player.pause() else player.play()
|
||||
}
|
||||
}
|
||||
}.launchIn(viewModelScope)
|
||||
}
|
||||
}
|
||||
|
||||
data class CurrentPlayback(
|
||||
|
|
|
|||
|
|
@ -0,0 +1,69 @@
|
|||
package com.github.damontecres.wholphin.util
|
||||
|
||||
import android.content.Context
|
||||
import android.widget.Toast
|
||||
import com.github.damontecres.wholphin.hilt.IoCoroutineScope
|
||||
import com.github.damontecres.wholphin.ui.showToast
|
||||
import dagger.hilt.android.qualifiers.ApplicationContext
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.flow.launchIn
|
||||
import kotlinx.coroutines.flow.onEach
|
||||
import kotlinx.coroutines.launch
|
||||
import org.jellyfin.sdk.api.client.ApiClient
|
||||
import org.jellyfin.sdk.api.client.extensions.sessionApi
|
||||
import org.jellyfin.sdk.api.sockets.subscribe
|
||||
import org.jellyfin.sdk.model.api.GeneralCommandMessage
|
||||
import org.jellyfin.sdk.model.api.GeneralCommandType
|
||||
import org.jellyfin.sdk.model.api.MediaType
|
||||
import timber.log.Timber
|
||||
import javax.inject.Inject
|
||||
import javax.inject.Singleton
|
||||
|
||||
@Singleton
|
||||
class ServerEventListener
|
||||
@Inject
|
||||
constructor(
|
||||
@param:ApplicationContext private val context: Context,
|
||||
private val api: ApiClient,
|
||||
@param:IoCoroutineScope private val scope: CoroutineScope,
|
||||
) {
|
||||
init {
|
||||
setupListeners()
|
||||
}
|
||||
|
||||
fun init() {
|
||||
scope.launch(ExceptionHandler()) {
|
||||
api.sessionApi.postCapabilities(
|
||||
playableMediaTypes = listOf(MediaType.VIDEO),
|
||||
supportedCommands =
|
||||
listOf(
|
||||
GeneralCommandType.DISPLAY_MESSAGE,
|
||||
GeneralCommandType.SEND_STRING,
|
||||
),
|
||||
supportsMediaControl = true,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
fun setupListeners() {
|
||||
Timber.v("Subscribing to WebSocket")
|
||||
api.webSocket
|
||||
.subscribe<GeneralCommandMessage>()
|
||||
.onEach { message ->
|
||||
if (message.data?.name in
|
||||
setOf(
|
||||
GeneralCommandType.DISPLAY_MESSAGE,
|
||||
GeneralCommandType.SEND_STRING,
|
||||
)
|
||||
) {
|
||||
val header = message.data?.arguments["Header"]
|
||||
val text =
|
||||
message.data?.arguments["Text"] ?: message.data?.arguments["String"]
|
||||
val toast =
|
||||
listOfNotNull(header, text)
|
||||
.joinToString("\n")
|
||||
showToast(context, toast, Toast.LENGTH_LONG)
|
||||
}
|
||||
}.launchIn(scope)
|
||||
}
|
||||
}
|
||||
|
|
@ -20,8 +20,6 @@ import org.jellyfin.sdk.model.extensions.inWholeTicks
|
|||
import timber.log.Timber
|
||||
import java.util.Timer
|
||||
import java.util.TimerTask
|
||||
import java.util.concurrent.atomic.AtomicBoolean
|
||||
import java.util.concurrent.atomic.AtomicLong
|
||||
import kotlin.time.Duration.Companion.milliseconds
|
||||
import kotlin.time.Duration.Companion.seconds
|
||||
|
||||
|
|
@ -38,11 +36,6 @@ class TrackActivityPlaybackListener(
|
|||
private val coroutineScope = CoroutineScope(Dispatchers.Main)
|
||||
private val task: TimerTask
|
||||
|
||||
private var totalPlayDurationMilliseconds = AtomicLong(0)
|
||||
private var currentDurationMilliseconds = AtomicLong(0)
|
||||
private var isPlaying = AtomicBoolean(false)
|
||||
private var incrementedPlayCount = AtomicBoolean(false)
|
||||
|
||||
init {
|
||||
coroutineScope.launch(Dispatchers.IO + ExceptionHandler()) {
|
||||
api.playStateApi.reportPlaybackStart(
|
||||
|
|
@ -60,28 +53,13 @@ class TrackActivityPlaybackListener(
|
|||
),
|
||||
)
|
||||
}
|
||||
val saveActivityInterval = 10.seconds.inWholeMilliseconds
|
||||
val delay = 1.seconds.inWholeMilliseconds
|
||||
val delay = 5.seconds.inWholeMilliseconds
|
||||
// Every x seconds, check if the video is playing
|
||||
task =
|
||||
object : TimerTask() {
|
||||
private var timestamp = System.currentTimeMillis()
|
||||
|
||||
override fun run() {
|
||||
try {
|
||||
val now = System.currentTimeMillis()
|
||||
if (isPlaying.get()) {
|
||||
val diffTime = now - timestamp
|
||||
// If it is playing, add the interval to currently tracked duration
|
||||
val current = currentDurationMilliseconds.addAndGet(diffTime)
|
||||
// TODO currentDuration.getAndUpdate would be better, but requires API 24+
|
||||
if (current >= saveActivityInterval) {
|
||||
// If the accumulated currently tracked duration > threshold, reset it and save activity
|
||||
totalPlayDurationMilliseconds.addAndGet(current)
|
||||
saveActivity(-1L)
|
||||
}
|
||||
}
|
||||
timestamp = now
|
||||
saveActivity(-1L)
|
||||
} catch (ex: Exception) {
|
||||
Timber.Forest.w(ex, "Exception during track activity timer")
|
||||
}
|
||||
|
|
@ -108,14 +86,7 @@ class TrackActivityPlaybackListener(
|
|||
}
|
||||
|
||||
override fun onIsPlayingChanged(isPlaying: Boolean) {
|
||||
this.isPlaying.set(isPlaying)
|
||||
if (!isPlaying) {
|
||||
val diff = currentDurationMilliseconds.getAndSet(0)
|
||||
if (diff > 0) {
|
||||
totalPlayDurationMilliseconds.addAndGet(diff)
|
||||
saveActivity(-1)
|
||||
}
|
||||
}
|
||||
saveActivity(-1)
|
||||
}
|
||||
|
||||
override fun onPlaybackStateChanged(playbackState: Int) {
|
||||
|
|
@ -127,12 +98,11 @@ class TrackActivityPlaybackListener(
|
|||
|
||||
private fun saveActivity(position: Long) {
|
||||
coroutineScope.launch(Dispatchers.IO + ExceptionHandler()) {
|
||||
// val totalDuration = totalPlayDurationMilliseconds.get()
|
||||
val calcPosition =
|
||||
withContext(Dispatchers.Main) {
|
||||
(if (position >= 0) position else player.currentPosition).milliseconds
|
||||
}
|
||||
Timber.v("saveActivity: itemId=${itemPlayback.itemId}, pos=$calcPosition")
|
||||
// Timber.v("saveActivity: itemId=${itemPlayback.itemId}, pos=$calcPosition")
|
||||
api.playStateApi.reportPlaybackProgress(
|
||||
PlaybackProgressInfo(
|
||||
itemId = itemPlayback.itemId,
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue