mirror of
https://github.com/JustinZeus/Wholphin.git
synced 2026-11-06 02:40:58 +01: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
|
|
@ -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<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) {
|
||||
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) {
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue