fix(android): tell external players to start unwatched items from the beginning
With an external player on Plex, starting a never-watched episode could open a "resume playback?" prompt at the position the previous, unrelated file was exited. The intent carried no start hint when the item had no resume point, so players with their own bookmark store (Zidoo native player, VLC) consulted it - and every Plex part URL ends in the same `file.<ext>`, so those bookmarks collide across items. Send `from_start=true` on fresh launches and `from_start=false` beside `position` when resuming, matching the Zidoo integrations in jellyfin-androidtv-zidoo and PlexToZidoo. MX, Just Player and mpv ignore the extra. close #2223
This commit is contained in:
@@ -28,6 +28,12 @@ internal class ExternalPlayerChannel(private val activity: Activity) {
|
||||
private const val API_VLC_RESULT_POSITION = "extra_position"
|
||||
private const val API_VLC_RESULT_DURATION = "extra_duration"
|
||||
|
||||
// Honored by VLC and the native Zidoo player (com.android.gallery3d /
|
||||
// com.zidoo.player). Without it, a launch with no resume point lets the
|
||||
// player consult its own bookmark store, which on Zidoo collides across
|
||||
// Plex items because every part URL ends in the same `file.<ext>` (#2223).
|
||||
private const val API_VLC_FROM_START = "from_start"
|
||||
|
||||
private const val API_VIMU_TITLE = "forcename"
|
||||
private const val API_VIMU_SEEK_POSITION = "startfrom"
|
||||
private const val API_VIMU_RESUME = "forceresume"
|
||||
@@ -150,7 +156,7 @@ internal class ExternalPlayerChannel(private val activity: Activity) {
|
||||
}
|
||||
}
|
||||
|
||||
private data class Source(val uri: Uri, val grantRead: Boolean, val fileName: String?)
|
||||
internal data class Source(val uri: Uri, val grantRead: Boolean, val fileName: String?)
|
||||
|
||||
private fun resolveSource(filePath: String): Source {
|
||||
if (filePath.startsWith("http://") || filePath.startsWith("https://")) {
|
||||
@@ -168,7 +174,7 @@ internal class ExternalPlayerChannel(private val activity: Activity) {
|
||||
return Source(uri, grantRead = true, fileName = file.name)
|
||||
}
|
||||
|
||||
private fun buildIntent(
|
||||
internal fun buildIntent(
|
||||
source: Source,
|
||||
packageName: String?,
|
||||
startPositionMs: Long,
|
||||
@@ -181,6 +187,9 @@ internal class ExternalPlayerChannel(private val activity: Activity) {
|
||||
if (startPosition > 0) {
|
||||
putExtra(API_MX_RESULT_POSITION, startPosition)
|
||||
putExtra(API_VIMU_SEEK_POSITION, startPosition)
|
||||
putExtra(API_VLC_FROM_START, false)
|
||||
} else {
|
||||
putExtra(API_VLC_FROM_START, true)
|
||||
}
|
||||
putExtra(API_MX_RETURN_RESULT, true)
|
||||
putExtra(API_MX_SECURE_URI, true)
|
||||
|
||||
@@ -2,8 +2,10 @@ package com.edde746.plezy
|
||||
|
||||
import android.app.Activity
|
||||
import android.content.Intent
|
||||
import android.net.Uri
|
||||
import io.flutter.plugin.common.MethodChannel
|
||||
import org.junit.Assert.assertEquals
|
||||
import org.junit.Assert.assertFalse
|
||||
import org.junit.Assert.assertTrue
|
||||
import org.junit.Test
|
||||
import org.junit.runner.RunWith
|
||||
@@ -30,6 +32,40 @@ class ExternalPlayerChannelTest {
|
||||
assertEquals(false, result["playbackError"])
|
||||
}
|
||||
|
||||
@Test
|
||||
fun freshLaunchTellsPlayerToStartFromBeginning() {
|
||||
val activity = Robolectric.buildActivity(Activity::class.java).get()
|
||||
val channel = ExternalPlayerChannel(activity)
|
||||
val source = ExternalPlayerChannel.Source(
|
||||
Uri.parse("http://plex:32400/library/parts/9808/1775431760/file.mkv?X-Plex-Token=tok"),
|
||||
grantRead = false,
|
||||
fileName = "file.mkv"
|
||||
)
|
||||
|
||||
val intent = channel.buildIntent(source, packageName = null, startPositionMs = 0L, title = "Episode")
|
||||
|
||||
assertTrue(intent.getBooleanExtra("from_start", false))
|
||||
assertFalse(intent.hasExtra("position"))
|
||||
assertFalse(intent.hasExtra("startfrom"))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun resumeLaunchPassesPositionAndDisablesFromStart() {
|
||||
val activity = Robolectric.buildActivity(Activity::class.java).get()
|
||||
val channel = ExternalPlayerChannel(activity)
|
||||
val source = ExternalPlayerChannel.Source(
|
||||
Uri.parse("http://plex:32400/library/parts/9808/1775431760/file.mkv?X-Plex-Token=tok"),
|
||||
grantRead = false,
|
||||
fileName = "file.mkv"
|
||||
)
|
||||
|
||||
val intent = channel.buildIntent(source, packageName = null, startPositionMs = 90_000L, title = null)
|
||||
|
||||
assertFalse(intent.getBooleanExtra("from_start", true))
|
||||
assertEquals(90_000, intent.getIntExtra("position", -1))
|
||||
assertEquals(90_000, intent.getIntExtra("startfrom", -1))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun activityDestroyCompletesPendingChannelCall() {
|
||||
val activity = Robolectric.buildActivity(Activity::class.java).get()
|
||||
|
||||
Reference in New Issue
Block a user