diff --git a/app/src/main/java/com/github/damontecres/wholphin/MainActivity.kt b/app/src/main/java/com/github/damontecres/wholphin/MainActivity.kt index f7182147..1fa290d0 100644 --- a/app/src/main/java/com/github/damontecres/wholphin/MainActivity.kt +++ b/app/src/main/java/com/github/damontecres/wholphin/MainActivity.kt @@ -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 @@ -152,12 +154,10 @@ class MainActivity : AppCompatActivity() { if (backStackStr != null) { Timber.d("Restoring back stack") var backStack = json.decodeFromString>(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 diff --git a/app/src/main/java/com/github/damontecres/wholphin/services/PlaybackLifecycleObserver.kt b/app/src/main/java/com/github/damontecres/wholphin/services/PlaybackLifecycleObserver.kt index 66fb851c..91c1e7cb 100644 --- a/app/src/main/java/com/github/damontecres/wholphin/services/PlaybackLifecycleObserver.kt +++ b/app/src/main/java/com/github/damontecres/wholphin/services/PlaybackLifecycleObserver.kt @@ -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 } diff --git a/app/src/main/java/com/github/damontecres/wholphin/ui/playback/PlayExternalPage.kt b/app/src/main/java/com/github/damontecres/wholphin/ui/playback/PlayExternalPage.kt index c14d184d..e3592372 100644 --- a/app/src/main/java/com/github/damontecres/wholphin/ui/playback/PlayExternalPage.kt +++ b/app/src/main/java/com/github/damontecres/wholphin/ui/playback/PlayExternalPage.kt @@ -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,19 +335,27 @@ class PlayExternalViewModel } } Timber.v("Result position: %s", position?.milliseconds) - api.playStateApi.reportPlaybackStopped( - PlaybackStopInfo( - itemId = itemId, - mediaSourceId = mediaSourceId, - positionTicks = position?.milliseconds?.inWholeTicks, - failed = false, - ), - ) + if (position != null || result.data?.action != null) { + api.playStateApi.reportPlaybackStopped( + PlaybackStopInfo( + itemId = itemId, + mediaSourceId = mediaSourceId, + positionTicks = position?.milliseconds?.inWholeTicks, + 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( - 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) { - LaunchedEffect(Unit) { - viewModel.init() + val launched by viewModel.launched.collectAsState() + LaunchedEffect(Unit) { + 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) {