mirror of
https://github.com/JustinZeus/Wholphin.git
synced 2026-07-08 15:50:16 +02:00
External player follow up fixes (#1294)
## Description Fixes some issues when using an external player: - Send playback started before launching so the last played date is updated - Only use VLC's position if >0 since it returns 0 when playback completes ### Related issues Follow up to #1256 ### Testing Emulator & nvidia shield ## Screenshots N/A ## AI or LLM usage None
This commit is contained in:
parent
0e37084a16
commit
ab5c884072
3 changed files with 76 additions and 50 deletions
|
|
@ -54,6 +54,7 @@ import com.github.damontecres.wholphin.ui.components.LoadingPage
|
|||
import com.github.damontecres.wholphin.ui.detail.series.SeasonEpisodeIds
|
||||
import com.github.damontecres.wholphin.ui.launchDefault
|
||||
import com.github.damontecres.wholphin.ui.nav.Destination
|
||||
import com.github.damontecres.wholphin.ui.playback.PlayExternalViewModel
|
||||
import com.github.damontecres.wholphin.ui.showToast
|
||||
import com.github.damontecres.wholphin.ui.theme.WholphinTheme
|
||||
import com.github.damontecres.wholphin.ui.util.ProvideLocalClock
|
||||
|
|
@ -82,6 +83,7 @@ import javax.inject.Inject
|
|||
@AndroidEntryPoint
|
||||
class MainActivity : AppCompatActivity() {
|
||||
private val viewModel: MainActivityViewModel by viewModels()
|
||||
private val playExternalViewModel: PlayExternalViewModel by viewModels()
|
||||
|
||||
@Inject
|
||||
lateinit var userPreferencesDataStore: DataStore<AppPreferences>
|
||||
|
|
@ -152,12 +154,10 @@ class MainActivity : AppCompatActivity() {
|
|||
if (backStackStr != null) {
|
||||
Timber.d("Restoring back stack")
|
||||
var backStack = json.decodeFromString<List<Destination>>(backStackStr)
|
||||
if (!savedInstanceState.getBoolean(KEY_EXTERNAL_PLAYER)) {
|
||||
|
||||
if (!playExternalViewModel.launched.value) {
|
||||
val lastDest = backStack.lastOrNull()
|
||||
if (lastDest is Destination.Playback ||
|
||||
lastDest is Destination.PlaybackList ||
|
||||
lastDest is Destination.Slideshow
|
||||
) {
|
||||
if (lastDest.isPlayback) {
|
||||
Timber.v("Restoring back stack with playback")
|
||||
backStack = backStack.toMutableList().apply { removeAt(lastIndex) }
|
||||
}
|
||||
|
|
@ -271,6 +271,14 @@ class MainActivity : AppCompatActivity() {
|
|||
super.onRestart()
|
||||
Timber.d("onRestart")
|
||||
viewModel.appStart()
|
||||
if (!playExternalViewModel.launched.value) {
|
||||
// If restarting during playback that is not external, go back a page
|
||||
val lastDest = navigationManager.backStack.lastOrNull()
|
||||
if (lastDest.isPlayback) {
|
||||
Timber.v("onRestart: go back from playback")
|
||||
navigationManager.goBack()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun onStop() {
|
||||
|
|
@ -471,3 +479,9 @@ class MainActivityViewModel
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
private val Destination?.isPlayback: Boolean
|
||||
get() =
|
||||
this is Destination.Playback ||
|
||||
this is Destination.PlaybackList ||
|
||||
this is Destination.Slideshow
|
||||
|
|
|
|||
|
|
@ -19,14 +19,6 @@ class PlaybackLifecycleObserver
|
|||
private var wasPlaying: Boolean? = null
|
||||
|
||||
override fun onStart(owner: LifecycleOwner) {
|
||||
// TODO
|
||||
// val lastDest = navigationManager.backStack.lastOrNull()
|
||||
// if (lastDest is Destination.Playback ||
|
||||
// lastDest is Destination.PlaybackList ||
|
||||
// lastDest is Destination.Slideshow
|
||||
// ) {
|
||||
// navigationManager.goBack()
|
||||
// }
|
||||
wasPlaying = null
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -8,14 +8,13 @@ import android.widget.Toast
|
|||
import androidx.activity.compose.rememberLauncherForActivityResult
|
||||
import androidx.activity.result.ActivityResult
|
||||
import androidx.activity.result.contract.ActivityResultContracts
|
||||
import androidx.appcompat.app.AppCompatActivity
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.LaunchedEffect
|
||||
import androidx.compose.runtime.collectAsState
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.saveable.rememberSaveable
|
||||
import androidx.compose.runtime.setValue
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
import androidx.core.net.toUri
|
||||
import androidx.hilt.lifecycle.viewmodel.compose.hiltViewModel
|
||||
import androidx.lifecycle.SavedStateHandle
|
||||
|
|
@ -33,6 +32,7 @@ import com.github.damontecres.wholphin.services.StreamChoiceService
|
|||
import com.github.damontecres.wholphin.services.UserPreferencesService
|
||||
import com.github.damontecres.wholphin.ui.components.ErrorMessage
|
||||
import com.github.damontecres.wholphin.ui.components.LoadingPage
|
||||
import com.github.damontecres.wholphin.ui.findActivity
|
||||
import com.github.damontecres.wholphin.ui.indexOfFirstOrNull
|
||||
import com.github.damontecres.wholphin.ui.isNotNullOrBlank
|
||||
import com.github.damontecres.wholphin.ui.launchDefault
|
||||
|
|
@ -40,9 +40,6 @@ import com.github.damontecres.wholphin.ui.nav.Destination
|
|||
import com.github.damontecres.wholphin.ui.preferences.getExternalPlayers
|
||||
import com.github.damontecres.wholphin.ui.showToast
|
||||
import com.github.damontecres.wholphin.util.LoadingState
|
||||
import dagger.assisted.Assisted
|
||||
import dagger.assisted.AssistedFactory
|
||||
import dagger.assisted.AssistedInject
|
||||
import dagger.hilt.android.lifecycle.HiltViewModel
|
||||
import dagger.hilt.android.qualifiers.ApplicationContext
|
||||
import kotlinx.coroutines.CancellationException
|
||||
|
|
@ -54,17 +51,22 @@ import org.jellyfin.sdk.api.client.extensions.subtitleApi
|
|||
import org.jellyfin.sdk.api.client.extensions.userLibraryApi
|
||||
import org.jellyfin.sdk.api.client.extensions.videosApi
|
||||
import org.jellyfin.sdk.model.api.MediaStream
|
||||
import org.jellyfin.sdk.model.api.PlayMethod
|
||||
import org.jellyfin.sdk.model.api.PlaybackOrder
|
||||
import org.jellyfin.sdk.model.api.PlaybackStartInfo
|
||||
import org.jellyfin.sdk.model.api.PlaybackStopInfo
|
||||
import org.jellyfin.sdk.model.api.RepeatMode
|
||||
import org.jellyfin.sdk.model.extensions.inWholeTicks
|
||||
import org.jellyfin.sdk.model.extensions.ticks
|
||||
import timber.log.Timber
|
||||
import java.io.File
|
||||
import java.util.UUID
|
||||
import javax.inject.Inject
|
||||
import kotlin.time.Duration.Companion.milliseconds
|
||||
|
||||
@HiltViewModel(assistedFactory = PlayExternalViewModel.Factory::class)
|
||||
@HiltViewModel
|
||||
class PlayExternalViewModel
|
||||
@AssistedInject
|
||||
@Inject
|
||||
constructor(
|
||||
private val savedStateHandle: SavedStateHandle,
|
||||
@param:ApplicationContext private val context: Context,
|
||||
|
|
@ -75,16 +77,13 @@ class PlayExternalViewModel
|
|||
private val streamChoiceService: StreamChoiceService,
|
||||
private val navigationManager: NavigationManager,
|
||||
private val userPreferencesService: UserPreferencesService,
|
||||
@Assisted val destination: Destination,
|
||||
) : ViewModel() {
|
||||
@AssistedFactory
|
||||
interface Factory {
|
||||
fun create(destination: Destination): PlayExternalViewModel
|
||||
}
|
||||
|
||||
val launched = savedStateHandle.getMutableStateFlow("launched", false)
|
||||
val state = MutableStateFlow(PlayExternalState())
|
||||
|
||||
fun init() {
|
||||
fun init(destination: Destination) {
|
||||
Timber.v("init called: %s", destination)
|
||||
state.update { it.copy(loading = LoadingState.Loading) }
|
||||
viewModelScope.launchDefault {
|
||||
val prefs = userPreferencesService.getCurrent()
|
||||
val positionMs: Long
|
||||
|
|
@ -252,7 +251,17 @@ class PlayExternalViewModel
|
|||
putExtra("forcedsrt", subtitleUrls[it])
|
||||
}
|
||||
}
|
||||
|
||||
api.playStateApi.reportPlaybackStart(
|
||||
PlaybackStartInfo(
|
||||
canSeek = false,
|
||||
itemId = itemId,
|
||||
isPaused = false,
|
||||
playMethod = PlayMethod.DIRECT_PLAY,
|
||||
repeatMode = RepeatMode.REPEAT_NONE,
|
||||
playbackOrder = PlaybackOrder.DEFAULT,
|
||||
isMuted = false,
|
||||
),
|
||||
)
|
||||
state.update {
|
||||
PlayExternalState(
|
||||
loading = LoadingState.Success,
|
||||
|
|
@ -278,10 +287,10 @@ class PlayExternalViewModel
|
|||
return@launchDefault
|
||||
}
|
||||
Timber.v(
|
||||
"Result: result=%s, itemId=%s action=%s",
|
||||
"Result: result=%s, action=%s, itemId=%s",
|
||||
result.resultCode,
|
||||
itemId,
|
||||
result.data?.action,
|
||||
itemId,
|
||||
)
|
||||
if (result.resultCode == Activity.RESULT_OK || result.resultCode == Activity.RESULT_CANCELED ||
|
||||
// Vimu return 1 for video completion
|
||||
|
|
@ -295,7 +304,7 @@ class PlayExternalViewModel
|
|||
position =
|
||||
data
|
||||
.getLongExtra("extra_position", Long.MIN_VALUE)
|
||||
.takeIf { it >= 0 }
|
||||
.takeIf { it > 0 }
|
||||
}
|
||||
|
||||
// mpv-android: https://mpv-android.github.io/mpv-android/intent.html
|
||||
|
|
@ -326,6 +335,7 @@ class PlayExternalViewModel
|
|||
}
|
||||
}
|
||||
Timber.v("Result position: %s", position?.milliseconds)
|
||||
if (position != null || result.data?.action != null) {
|
||||
api.playStateApi.reportPlaybackStopped(
|
||||
PlaybackStopInfo(
|
||||
itemId = itemId,
|
||||
|
|
@ -334,11 +344,18 @@ class PlayExternalViewModel
|
|||
failed = false,
|
||||
),
|
||||
)
|
||||
}
|
||||
} else {
|
||||
Timber.w("Activity result: %s", result.resultCode)
|
||||
Timber.w(
|
||||
"Activity result: %s, action=%s",
|
||||
result.resultCode,
|
||||
result.data?.action,
|
||||
)
|
||||
showToast(context, "Unknown result from external player")
|
||||
}
|
||||
navigationManager.goBack()
|
||||
state.update { PlayExternalState() }
|
||||
launched.update { false }
|
||||
} catch (_: CancellationException) {
|
||||
} catch (ex: Exception) {
|
||||
Timber.e(ex, "Error during external playback of %s", itemId)
|
||||
|
|
@ -359,7 +376,7 @@ class PlayExternalViewModel
|
|||
}
|
||||
|
||||
data class PlayExternalState(
|
||||
val loading: LoadingState = LoadingState.Loading,
|
||||
val loading: LoadingState = LoadingState.Pending,
|
||||
val intent: Intent = Intent(),
|
||||
)
|
||||
|
||||
|
|
@ -369,8 +386,8 @@ fun PlayExternalPage(
|
|||
destination: Destination,
|
||||
modifier: Modifier = Modifier,
|
||||
viewModel: PlayExternalViewModel =
|
||||
hiltViewModel<PlayExternalViewModel, PlayExternalViewModel.Factory>(
|
||||
creationCallback = { it.create(destination) },
|
||||
hiltViewModel(
|
||||
viewModelStoreOwner = LocalContext.current.findActivity() as AppCompatActivity,
|
||||
),
|
||||
) {
|
||||
val launcher =
|
||||
|
|
@ -380,15 +397,18 @@ fun PlayExternalPage(
|
|||
)
|
||||
|
||||
val state by viewModel.state.collectAsState()
|
||||
var launched by rememberSaveable { mutableStateOf(false) }
|
||||
if (!launched) {
|
||||
val launched by viewModel.launched.collectAsState()
|
||||
LaunchedEffect(Unit) {
|
||||
viewModel.init()
|
||||
if (!launched) {
|
||||
viewModel.init(destination)
|
||||
}
|
||||
}
|
||||
|
||||
when (val l = state.loading) {
|
||||
LoadingState.Pending,
|
||||
LoadingState.Pending -> {
|
||||
LoadingPage(modifier, false)
|
||||
}
|
||||
|
||||
LoadingState.Loading,
|
||||
-> {
|
||||
LoadingPage(modifier)
|
||||
|
|
@ -403,7 +423,7 @@ fun PlayExternalPage(
|
|||
if (!launched) {
|
||||
LifecycleStartEffect(Unit) {
|
||||
Timber.i("Launching external playback")
|
||||
launched = true
|
||||
viewModel.launched.update { true }
|
||||
try {
|
||||
launcher.launch(state.intent)
|
||||
} catch (ex: Exception) {
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue