Update wholphin-extensions with newer MpvPlayer (#1365)

## Description
Uses the newer version of `wholphin-extensions` which now has the Kotlin
code, so this PR removes the MPV Kotlin code from this repo. Also
updates gradle to use providers.

There are no user facing changes

### Dev notes

`wholphin-mpv` can be provided by:
1. [Fetching pre-built
binaries](https://github.com/damontecres/wholphin-extensions#pre-built-binaries)
(Preferred method for devs)
2. Building `wholphin-extensions` from
[source](https://github.com/damontecres/wholphin-extensions#build-from-source)
3. Using `wholphin-mpv-stub` (the new module added in this PR) which
allows for compiling Wholphin, but will throw exceptions if MPV is used
during runtime

### Related issues
Related to #1172 

### Testing
Emulator

## Screenshots
N/A

## AI or LLM usage
None
This commit is contained in:
Damontecres 2026-06-26 12:50:45 -04:00 • committed by GitHub
parent f5ce9745ba
commit a8ade9171e
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
24 changed files with 177 additions and 1652 deletions

View file

@ -42,3 +42,6 @@ jobs:
./gradlew clean assembleDefaultDebug testDefaultDebugUnitTest --no-daemon ./gradlew clean assembleDefaultDebug testDefaultDebugUnitTest --no-daemon
apks=$(find app/build/outputs \( -name '*.apk' -or -name '*.aab' \) -print0 | tr '\0' ',' | sed 's/,$//') apks=$(find app/build/outputs \( -name '*.apk' -or -name '*.aab' \) -print0 | tr '\0' ',' | sed 's/,$//')
echo "apks=$apks" >> "$GITHUB_OUTPUT" echo "apks=$apks" >> "$GITHUB_OUTPUT"
env:
ORG_GRADLE_PROJECT_WholphinExtensionsUsername: "${{ secrets.EXTENSIONS_USERNAME }}"
ORG_GRADLE_PROJECT_WholphinExtensionsPassword: "${{ secrets.EXTENSIONS_PASSWORD }}"

View file

@ -42,6 +42,8 @@ If you want to include these in a local build, see the [instructions here](https
You can also build the extensions locally from https://github.com/damontecres/wholphin-extensions and include them in `app/libs`. The gradle build dependency resolution prefers these local files over fetching from the remote maven registry. You can also build the extensions locally from https://github.com/damontecres/wholphin-extensions and include them in `app/libs`. The gradle build dependency resolution prefers these local files over fetching from the remote maven registry.
Finally, if no `wholphin-mpv` implementation is found, `wholphin-mpv-stub` will be used. This allows the app to compile, but any runtime usage of MPV will throw an exception.
## Code organization ## Code organization
Code is split into several packages: Code is split into several packages:

View file

@ -20,12 +20,21 @@ plugins {
alias(libs.plugins.openapi.generator) alias(libs.plugins.openapi.generator)
} }
val isCI = if (System.getenv("CI") != null) System.getenv("CI").toBoolean() else false val isCI = providers.environmentVariable("CI").orElse("false").map { it.toBoolean() }
val shouldSign = isCI && System.getenv("KEY_ALIAS") != null val shouldSign =
val ffmpegModuleExists = project.file("libs/lib-decoder-ffmpeg-release.aar").exists() isCI.zip(
val av1ModuleExists = project.file("libs/lib-decoder-av1-release.aar").exists() providers.environmentVariable("KEY_ALIAS").orElse("").map { it.isNotBlank() },
val mpvModuleExists = project.file("libs/wholphin-mpv-release.aar").exists() ) { isCI, hasKey ->
val extensionsRepoActive = project.hasProperty("WholphinExtensionsUsername") isCI && hasKey
}
val ffmpegModuleExists =
providers.provider { project.file("libs/lib-decoder-ffmpeg-release.aar").exists() }
val av1ModuleExists =
providers.provider { project.file("libs/lib-decoder-av1-release.aar").exists() }
val mpvModuleExists =
providers.provider { project.file("libs/wholphin-mpv-release.aar").exists() }
val extensionsRepoActive =
providers.provider { project.hasProperty("WholphinExtensionsUsername") }
// See https://issuetracker.google.com/issues/402800800 // See https://issuetracker.google.com/issues/402800800
val isBuildingBundle = val isBuildingBundle =
@ -69,7 +78,7 @@ configure<ApplicationExtension> {
} }
signingConfigs { signingConfigs {
if (shouldSign) { if (shouldSign.get()) {
create("ci") { create("ci") {
file("ci.keystore").writeBytes( file("ci.keystore").writeBytes(
Base64.getDecoder().decode(System.getenv("SIGNING_KEY")), Base64.getDecoder().decode(System.getenv("SIGNING_KEY")),
@ -95,7 +104,7 @@ configure<ApplicationExtension> {
"proguard-rules.pro", "proguard-rules.pro",
) )
isDebuggable = false isDebuggable = false
if (shouldSign) { if (shouldSign.get()) {
signingConfig = signingConfigs.getByName("ci") signingConfig = signingConfigs.getByName("ci")
} else { } else {
val localPropertiesFile = project.rootProject.file("local.properties") val localPropertiesFile = project.rootProject.file("local.properties")
@ -357,32 +366,33 @@ dependencies {
debugImplementation(libs.androidx.compose.ui.test.manifest) debugImplementation(libs.androidx.compose.ui.test.manifest)
coreLibraryDesugaring(libs.desugar.jdk.libs) coreLibraryDesugaring(libs.desugar.jdk.libs)
if (ffmpegModuleExists) { if (ffmpegModuleExists.get()) {
logger.info("Using local ffmpeg decoder") logger.info("Using local ffmpeg decoder")
implementation(files("libs/lib-decoder-ffmpeg-release.aar")) implementation(files("libs/lib-decoder-ffmpeg-release.aar"))
} else if (extensionsRepoActive) { } else if (extensionsRepoActive.get()) {
logger.info("Using prebuilt ffmpeg decoder") logger.info("Using prebuilt ffmpeg decoder")
implementation(libs.wholphin.extensions.ffmpeg) implementation(libs.wholphin.extensions.ffmpeg)
} else { } else {
logger.warn("Media3 ffmpeg decoder was NOT found") logger.warn("Media3 ffmpeg decoder was NOT found")
} }
if (av1ModuleExists) { if (av1ModuleExists.get()) {
logger.info("Using local av1 decoder") logger.info("Using local av1 decoder")
implementation(files("libs/lib-decoder-av1-release.aar")) implementation(files("libs/lib-decoder-av1-release.aar"))
} else if (extensionsRepoActive) { } else if (extensionsRepoActive.get()) {
logger.info("Using prebuilt av1 decoder") logger.info("Using prebuilt av1 decoder")
implementation(libs.wholphin.extensions.av1) implementation(libs.wholphin.extensions.av1)
} else { } else {
logger.warn("Media3 av1 decoder was NOT found") logger.warn("Media3 av1 decoder was NOT found")
} }
if (mpvModuleExists) { if (mpvModuleExists.get()) {
logger.info("Using local libMPV build") logger.info("Using local libMPV build")
implementation(files("libs/wholphin-mpv-release.aar")) implementation(files("libs/wholphin-mpv-release.aar"))
} else if (extensionsRepoActive) { } else if (extensionsRepoActive.get()) {
logger.info("Using prebuilt libMPV") logger.info("Using prebuilt libMPV")
implementation(libs.wholphin.extensions.mpv) implementation(libs.wholphin.extensions.mpv)
} else { } else {
logger.warn("libMPV was NOT found") logger.warn("libMPV was NOT found, using stub library")
implementation(project(":wholphin-mpv-stub"))
} }
testImplementation(libs.mockk.android) testImplementation(libs.mockk.android)

View file

@ -25,13 +25,13 @@ import androidx.media3.exoplayer.video.MediaCodecVideoRenderer
import androidx.media3.exoplayer.video.VideoRendererEventListener import androidx.media3.exoplayer.video.VideoRendererEventListener
import androidx.media3.extractor.DefaultExtractorsFactory import androidx.media3.extractor.DefaultExtractorsFactory
import androidx.media3.session.MediaSession import androidx.media3.session.MediaSession
import com.github.damontecres.wholphin.mpv.MpvPlayer
import com.github.damontecres.wholphin.preferences.AssPlaybackMode import com.github.damontecres.wholphin.preferences.AssPlaybackMode
import com.github.damontecres.wholphin.preferences.MediaExtensionStatus import com.github.damontecres.wholphin.preferences.MediaExtensionStatus
import com.github.damontecres.wholphin.preferences.PlaybackPreferences import com.github.damontecres.wholphin.preferences.PlaybackPreferences
import com.github.damontecres.wholphin.preferences.PlayerBackend import com.github.damontecres.wholphin.preferences.PlayerBackend
import com.github.damontecres.wholphin.services.hilt.AuthOkHttpClient import com.github.damontecres.wholphin.services.hilt.AuthOkHttpClient
import com.github.damontecres.wholphin.util.WholphinDispatchers import com.github.damontecres.wholphin.util.WholphinDispatchers
import com.github.damontecres.wholphin.util.mpv.MpvPlayer
import dagger.hilt.android.qualifiers.ApplicationContext import dagger.hilt.android.qualifiers.ApplicationContext
import io.github.peerless2012.ass.media.AssHandler import io.github.peerless2012.ass.media.AssHandler
import io.github.peerless2012.ass.media.factory.AssRenderersFactory import io.github.peerless2012.ass.media.factory.AssRenderersFactory

View file

@ -67,6 +67,7 @@ import androidx.media3.ui.compose.state.rememberPlayPauseButtonState
import androidx.media3.ui.compose.state.rememberPresentationState import androidx.media3.ui.compose.state.rememberPresentationState
import androidx.tv.material3.MaterialTheme import androidx.tv.material3.MaterialTheme
import androidx.tv.material3.surfaceColorAtElevation import androidx.tv.material3.surfaceColorAtElevation
import com.github.damontecres.wholphin.mpv.MpvPlayer
import com.github.damontecres.wholphin.preferences.AssPlaybackMode import com.github.damontecres.wholphin.preferences.AssPlaybackMode
import com.github.damontecres.wholphin.preferences.PlayerBackend import com.github.damontecres.wholphin.preferences.PlayerBackend
import com.github.damontecres.wholphin.preferences.UserPreferences import com.github.damontecres.wholphin.preferences.UserPreferences
@ -90,7 +91,6 @@ import com.github.damontecres.wholphin.ui.tryRequestFocus
import com.github.damontecres.wholphin.util.ExceptionHandler import com.github.damontecres.wholphin.util.ExceptionHandler
import com.github.damontecres.wholphin.util.LoadingState import com.github.damontecres.wholphin.util.LoadingState
import com.github.damontecres.wholphin.util.Media3SubtitleOverride import com.github.damontecres.wholphin.util.Media3SubtitleOverride
import com.github.damontecres.wholphin.util.mpv.MpvPlayer
import io.github.peerless2012.ass.media.widget.AssSubtitleView import io.github.peerless2012.ass.media.widget.AssSubtitleView
import kotlinx.coroutines.delay import kotlinx.coroutines.delay
import kotlinx.coroutines.launch import kotlinx.coroutines.launch

View file

@ -35,6 +35,7 @@ import com.github.damontecres.wholphin.data.model.ItemPlayback
import com.github.damontecres.wholphin.data.model.Playlist import com.github.damontecres.wholphin.data.model.Playlist
import com.github.damontecres.wholphin.data.model.PlaylistItem import com.github.damontecres.wholphin.data.model.PlaylistItem
import com.github.damontecres.wholphin.data.model.TrackIndex import com.github.damontecres.wholphin.data.model.TrackIndex
import com.github.damontecres.wholphin.mpv.MpvPlayer
import com.github.damontecres.wholphin.preferences.AppPreference import com.github.damontecres.wholphin.preferences.AppPreference
import com.github.damontecres.wholphin.preferences.PlayerBackend import com.github.damontecres.wholphin.preferences.PlayerBackend
import com.github.damontecres.wholphin.preferences.ShowNextUpWhen import com.github.damontecres.wholphin.preferences.ShowNextUpWhen
@ -69,7 +70,6 @@ import com.github.damontecres.wholphin.util.PlaybackItemState
import com.github.damontecres.wholphin.util.TrackActivityPlaybackListener import com.github.damontecres.wholphin.util.TrackActivityPlaybackListener
import com.github.damontecres.wholphin.util.WholphinDispatchers import com.github.damontecres.wholphin.util.WholphinDispatchers
import com.github.damontecres.wholphin.util.checkForSupport import com.github.damontecres.wholphin.util.checkForSupport
import com.github.damontecres.wholphin.util.mpv.MpvPlayer
import com.github.damontecres.wholphin.util.mpv.mpvDeviceProfile import com.github.damontecres.wholphin.util.mpv.mpvDeviceProfile
import com.github.damontecres.wholphin.util.profile.Codec import com.github.damontecres.wholphin.util.profile.Codec
import com.github.damontecres.wholphin.util.subtitleMimeTypes import com.github.damontecres.wholphin.util.subtitleMimeTypes

View file

@ -15,6 +15,7 @@ import androidx.media3.common.util.UnstableApi
import androidx.media3.ui.CaptionStyleCompat import androidx.media3.ui.CaptionStyleCompat
import androidx.media3.ui.SubtitleView import androidx.media3.ui.SubtitleView
import com.github.damontecres.wholphin.R import com.github.damontecres.wholphin.R
import com.github.damontecres.wholphin.mpv.MPVLib
import com.github.damontecres.wholphin.preferences.AppChoicePreference import com.github.damontecres.wholphin.preferences.AppChoicePreference
import com.github.damontecres.wholphin.preferences.AppClickablePreference import com.github.damontecres.wholphin.preferences.AppClickablePreference
import com.github.damontecres.wholphin.preferences.AppDestinationPreference import com.github.damontecres.wholphin.preferences.AppDestinationPreference
@ -29,7 +30,6 @@ import com.github.damontecres.wholphin.ui.indexOfFirstOrNull
import com.github.damontecres.wholphin.ui.nav.Destination import com.github.damontecres.wholphin.ui.nav.Destination
import com.github.damontecres.wholphin.ui.preferences.ConditionalPreferences import com.github.damontecres.wholphin.ui.preferences.ConditionalPreferences
import com.github.damontecres.wholphin.ui.preferences.PreferenceGroup import com.github.damontecres.wholphin.ui.preferences.PreferenceGroup
import com.github.damontecres.wholphin.util.mpv.MPVLib
import com.github.damontecres.wholphin.util.mpv.setPropertyColor import com.github.damontecres.wholphin.util.mpv.setPropertyColor
import timber.log.Timber import timber.log.Timber

View file

@ -1,311 +0,0 @@
package com.github.damontecres.wholphin.util.mpv
/*
This file is copied from https://github.com/mpv-android/mpv-android/blob/master/app/src/main/java/is/xyz/mpv/MPVLib.kt
Copyright (c) 2016 Ilya Zhuravlev
Copyright (c) 2016 sfan5 <sfan5@live.de>
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and
associated documentation files (the "Software"), to deal in the Software without restriction,
including without limitation the rights to use, copy, modify, merge, publish, distribute,
sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all copies or
substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT
NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
import android.content.Context
import android.graphics.Bitmap
import android.view.Surface
import java.util.concurrent.CopyOnWriteArrayList
// Wrapper for native library
@Suppress("unused")
object MPVLib {
init {
val libs = arrayOf("mpv", "player")
for (lib in libs) {
System.loadLibrary(lib)
}
}
external fun create(appctx: Context)
external fun init()
external fun destroy()
external fun attachSurface(surface: Surface)
external fun detachSurface()
external fun command(cmd: Array<out String>)
external fun setOptionString(
name: String,
value: String,
): Int
external fun grabThumbnail(dimension: Int): Bitmap?
external fun getPropertyInt(property: String): Int?
external fun setPropertyInt(
property: String,
value: Int,
)
external fun getPropertyDouble(property: String): Double?
external fun setPropertyDouble(
property: String,
value: Double,
)
external fun getPropertyBoolean(property: String): Boolean?
external fun setPropertyBoolean(
property: String,
value: Boolean,
)
external fun getPropertyString(property: String): String?
external fun setPropertyString(
property: String,
value: String,
)
external fun observeProperty(
property: String,
format: Int,
)
private val observers = CopyOnWriteArrayList<EventObserver>()
@JvmStatic
fun addObserver(o: EventObserver) {
observers.add(o)
}
@JvmStatic
fun removeObserver(o: EventObserver) {
observers.remove(o)
}
@JvmStatic
fun eventProperty(
property: String,
value: Long,
) {
for (o in observers) {
o.eventProperty(property, value)
}
}
@JvmStatic
fun eventProperty(
property: String,
value: Boolean,
) {
for (o in observers) {
o.eventProperty(property, value)
}
}
@JvmStatic
fun eventProperty(
property: String,
value: Double,
) {
for (o in observers) {
o.eventProperty(property, value)
}
}
@JvmStatic
fun eventProperty(
property: String,
value: String,
) {
for (o in observers) {
o.eventProperty(property, value)
}
}
@JvmStatic
fun eventProperty(property: String) {
for (o in observers) {
o.eventProperty(property)
}
}
@JvmStatic
fun event(eventId: Int) {
for (o in observers) {
o.event(eventId)
}
}
@JvmStatic
fun eventEndFile(
reason: Int,
error: Int,
) {
for (o in observers) {
o.eventEndFile(reason, error)
}
}
private val log_observers = CopyOnWriteArrayList<LogObserver>()
@JvmStatic
fun addLogObserver(o: LogObserver) {
log_observers.add(o)
}
@JvmStatic
fun removeLogObserver(o: LogObserver) {
log_observers.remove(o)
}
@JvmStatic
fun logMessage(
prefix: String,
level: Int,
text: String,
) {
for (o in log_observers) {
o.logMessage(prefix, level, text)
}
}
interface EventObserver {
fun eventProperty(property: String)
fun eventProperty(
property: String,
value: Long,
)
fun eventProperty(
property: String,
value: Boolean,
)
fun eventProperty(
property: String,
value: String,
)
fun eventProperty(
property: String,
value: Double,
)
fun event(eventId: Int)
fun eventEndFile(
reason: Int,
error: Int,
)
}
interface LogObserver {
fun logMessage(
prefix: String,
level: Int,
text: String,
)
}
object MpvFormat {
const val MPV_FORMAT_NONE: Int = 0
const val MPV_FORMAT_STRING: Int = 1
const val MPV_FORMAT_OSD_STRING: Int = 2
const val MPV_FORMAT_FLAG: Int = 3
const val MPV_FORMAT_INT64: Int = 4
const val MPV_FORMAT_DOUBLE: Int = 5
const val MPV_FORMAT_NODE: Int = 6
const val MPV_FORMAT_NODE_ARRAY: Int = 7
const val MPV_FORMAT_NODE_MAP: Int = 8
const val MPV_FORMAT_BYTE_ARRAY: Int = 9
}
object MpvEvent {
const val MPV_EVENT_NONE: Int = 0
const val MPV_EVENT_SHUTDOWN: Int = 1
const val MPV_EVENT_LOG_MESSAGE: Int = 2
const val MPV_EVENT_GET_PROPERTY_REPLY: Int = 3
const val MPV_EVENT_SET_PROPERTY_REPLY: Int = 4
const val MPV_EVENT_COMMAND_REPLY: Int = 5
const val MPV_EVENT_START_FILE: Int = 6
const val MPV_EVENT_END_FILE: Int = 7
const val MPV_EVENT_FILE_LOADED: Int = 8
@Deprecated("")
const val MPV_EVENT_IDLE: Int = 11
@Deprecated("")
const val MPV_EVENT_TICK: Int = 14
const val MPV_EVENT_CLIENT_MESSAGE: Int = 16
const val MPV_EVENT_VIDEO_RECONFIG: Int = 17
const val MPV_EVENT_AUDIO_RECONFIG: Int = 18
const val MPV_EVENT_SEEK: Int = 20
const val MPV_EVENT_PLAYBACK_RESTART: Int = 21
const val MPV_EVENT_PROPERTY_CHANGE: Int = 22
const val MPV_EVENT_QUEUE_OVERFLOW: Int = 24
const val MPV_EVENT_HOOK: Int = 25
}
object MpvLogLevel {
const val MPV_LOG_LEVEL_NONE: Int = 0
const val MPV_LOG_LEVEL_FATAL: Int = 10
const val MPV_LOG_LEVEL_ERROR: Int = 20
const val MPV_LOG_LEVEL_WARN: Int = 30
const val MPV_LOG_LEVEL_INFO: Int = 40
const val MPV_LOG_LEVEL_V: Int = 50
const val MPV_LOG_LEVEL_DEBUG: Int = 60
const val MPV_LOG_LEVEL_TRACE: Int = 70
}
object MpvEndFileReason {
const val MPV_END_FILE_REASON_EOF: Int = 0
const val MPV_END_FILE_REASON_STOP: Int = 2
const val MPV_END_FILE_REASON_QUIT: Int = 3
const val MPV_END_FILE_REASON_ERROR: Int = 4
const val MPV_END_FILE_REASON_REDIRECT: Int = 5
}
object MpvError {
const val MPV_ERROR_SUCCESS = 0
const val MPV_ERROR_EVENT_QUEUE_FULL = -1
const val MPV_ERROR_NOMEM = -2
const val MPV_ERROR_UNINITIALIZED = -3
const val MPV_ERROR_INVALID_PARAMETER = -4
const val MPV_ERROR_OPTION_NOT_FOUND = -5
const val MPV_ERROR_OPTION_FORMAT = -6
const val MPV_ERROR_OPTION_ERROR = -7
const val MPV_ERROR_PROPERTY_NOT_FOUND = -8
const val MPV_ERROR_PROPERTY_FORMAT = -9
const val MPV_ERROR_PROPERTY_UNAVAILABLE = -10
const val MPV_ERROR_PROPERTY_ERROR = -11
const val MPV_ERROR_COMMAND = -12
const val MPV_ERROR_LOADING_FAILED = -13
const val MPV_ERROR_AO_INIT_FAILED = -14
const val MPV_ERROR_VO_INIT_FAILED = -15
const val MPV_ERROR_NOTHING_TO_PLAY = -16
const val MPV_ERROR_UNKNOWN_FORMAT = -17
const val MPV_ERROR_UNSUPPORTED = -18
const val MPV_ERROR_NOT_IMPLEMENTED = -19
const val MPV_ERROR_GENERIC = -20
}
}

View file

@ -1,46 +0,0 @@
package com.github.damontecres.wholphin.util.mpv
import com.github.damontecres.wholphin.util.mpv.MPVLib.MpvFormat.MPV_FORMAT_DOUBLE
import com.github.damontecres.wholphin.util.mpv.MPVLib.MpvFormat.MPV_FORMAT_FLAG
import com.github.damontecres.wholphin.util.mpv.MPVLib.MpvFormat.MPV_FORMAT_INT64
import com.github.damontecres.wholphin.util.mpv.MPVLib.MpvFormat.MPV_FORMAT_NONE
import com.github.damontecres.wholphin.util.mpv.MPVLib.MpvFormat.MPV_FORMAT_STRING
object MPVProperty {
const val POSITION = "time-pos"
const val POSITION_FULL = "time-pos/full"
const val DURATION = "duration/full"
const val PAUSED = "pause"
const val PAUSED_FOR_CACHE = "paused-for-cache"
const val SPEED = "speed"
const val TRACK_LIST = "track-list"
const val ASPECT = "video-params/aspect"
const val ROTATE = "video-params/rotate"
const val TRACK_VIDEO = "current-tracks/video/image"
const val METADATA = "metadata"
const val HWDEC = "hwdec-current"
const val MUTE = "mute"
const val TRACK_AUDIO = "current-tracks/audio/selected"
const val SEEKABLE = "seekable"
const val SUBTITLE_TEXT = "sub-text"
val observedProperties =
mapOf(
POSITION to MPV_FORMAT_INT64,
// POSITION_FULL to MPV_FORMAT_DOUBLE,
DURATION to MPV_FORMAT_DOUBLE,
PAUSED to MPV_FORMAT_FLAG,
PAUSED_FOR_CACHE to MPV_FORMAT_FLAG,
SPEED to MPV_FORMAT_STRING,
TRACK_LIST to MPV_FORMAT_NONE,
ASPECT to MPV_FORMAT_DOUBLE,
ROTATE to MPV_FORMAT_DOUBLE,
TRACK_VIDEO to MPV_FORMAT_NONE,
METADATA to MPV_FORMAT_NONE,
HWDEC to MPV_FORMAT_NONE,
MUTE to MPV_FORMAT_FLAG,
TRACK_AUDIO to MPV_FORMAT_NONE,
SEEKABLE to MPV_FORMAT_FLAG,
SUBTITLE_TEXT to MPV_FORMAT_STRING,
)
}

View file

@ -1,21 +0,0 @@
package com.github.damontecres.wholphin.util.mpv
import timber.log.Timber
class MpvLogger : MPVLib.LogObserver {
override fun logMessage(
prefix: String,
level: Int,
text: String,
) {
// https://github.com/mpv-player/mpv/blob/122abdfec3124bfc92a2918a70ca8150eee68338/include/mpv/client.h#L1423
when {
level <= 10 -> Timber.wtf("%s, %s", prefix, text)
level <= 20 -> Timber.e("%s, %s", prefix, text)
level <= 30 -> Timber.w("%s, %s", prefix, text)
level <= 40 -> Timber.i("%s, %s", prefix, text)
level <= 50 -> Timber.d("%s, %s", prefix, text)
else -> Timber.v("%s, %s", prefix, text)
}
}
}

View file

@ -0,0 +1,11 @@
package com.github.damontecres.wholphin.util.mpv
import androidx.compose.ui.graphics.Color
import com.github.damontecres.wholphin.mpv.MPVLib
fun MPVLib.setPropertyColor(
property: String,
color: Color,
) = setPropertyString(property, color.mpvFormat)
private val Color.mpvFormat: String get() = "$red/$green/$blue/$alpha"

View file

@ -6,4 +6,5 @@ plugins {
alias(libs.plugins.ksp) apply false alias(libs.plugins.ksp) apply false
alias(libs.plugins.kotlin.compose) apply false alias(libs.plugins.kotlin.compose) apply false
alias(libs.plugins.hilt) apply false alias(libs.plugins.hilt) apply false
alias(libs.plugins.android.library) apply false
} }

View file

@ -45,7 +45,11 @@ kotlinxCoroutinesTest = "1.11.0"
coreTesting = "2.2.0" coreTesting = "2.2.0"
openapi-generator = "7.23.0" openapi-generator = "7.23.0"
runner = "1.7.0" runner = "1.7.0"
wholphin-extensions = "0.1.3" wholphin-extensions = "0.2.1"
junit = "4.13.2"
junitVersion = "1.3.0"
espressoCore = "3.7.0"
material = "1.13.0"
[libraries] [libraries]
wholphin-extensions-mpv = { module = "com.github.damontecres.wholphin.mpv:wholphin-mpv", version.ref = "wholphin-extensions" } wholphin-extensions-mpv = { module = "com.github.damontecres.wholphin.mpv:wholphin-mpv", version.ref = "wholphin-extensions" }
@ -139,6 +143,10 @@ androidx-core-testing = { module = "androidx.arch.core:core-testing", version.re
androidx-runner = { group = "androidx.test", name = "runner", version.ref = "runner" } androidx-runner = { group = "androidx.test", name = "runner", version.ref = "runner" }
ass-media = { group = "io.github.peerless2012", name = "ass-media", version.ref = "assMedia" } ass-media = { group = "io.github.peerless2012", name = "ass-media", version.ref = "assMedia" }
junit = { group = "junit", name = "junit", version.ref = "junit" }
androidx-junit = { group = "androidx.test.ext", name = "junit", version.ref = "junitVersion" }
androidx-espresso-core = { group = "androidx.test.espresso", name = "espresso-core", version.ref = "espressoCore" }
material = { group = "com.google.android.material", name = "material", version.ref = "material" }
[plugins] [plugins]
android-application = { id = "com.android.application", version.ref = "agp" } android-application = { id = "com.android.application", version.ref = "agp" }
@ -151,3 +159,4 @@ hilt = { id = "com.google.dagger.hilt.android", version.ref = "hilt" }
room = { id = "androidx.room", version.ref = "room" } room = { id = "androidx.room", version.ref = "room" }
aboutLibraries = { id = "com.mikepenz.aboutlibraries.plugin.android", version.ref = "aboutLibraries" } aboutLibraries = { id = "com.mikepenz.aboutlibraries.plugin.android", version.ref = "aboutLibraries" }
openapi-generator = { id = "org.openapi.generator", version.ref = "openapi-generator" } openapi-generator = { id = "org.openapi.generator", version.ref = "openapi-generator" }
android-library = { id = "com.android.library", version.ref = "agp" }

View file

@ -29,3 +29,4 @@ dependencyResolutionManagement {
rootProject.name = "Wholphin" rootProject.name = "Wholphin"
include(":app") include(":app")
include(":wholphin-mpv-stub")

1
wholphin-mpv-stub/.gitignore vendored Normal file
View file

@ -0,0 +1 @@
/build

View file

@ -0,0 +1,5 @@
# wholphin-mpv-stub
This is a non-functional stub implementation for the `wholphin-mpv` library in https://github.com/damontecres/wholphin-extensions.
This provides the minimal API so that Wholphin can compile without `wholphin-mpv`, but any runtime usage will error out.

View file

@ -0,0 +1,38 @@
plugins {
alias(libs.plugins.android.library)
}
android {
namespace = "com.github.damontecres.wholphin.mpv"
compileSdk {
version = release(36)
}
defaultConfig {
minSdk = 23
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
consumerProguardFiles("consumer-rules.pro")
}
buildTypes {
release {
isMinifyEnabled = false
proguardFiles(
getDefaultProguardFile("proguard-android-optimize.txt"),
"proguard-rules.pro",
)
}
}
compileOptions {
sourceCompatibility = JavaVersion.VERSION_11
targetCompatibility = JavaVersion.VERSION_11
}
}
dependencies {
implementation(libs.androidx.media3.exoplayer)
testImplementation(libs.junit)
androidTestImplementation(libs.androidx.junit)
androidTestImplementation(libs.androidx.espresso.core)
}

View file

21
wholphin-mpv-stub/proguard-rules.pro vendored Normal file
View file

@ -0,0 +1,21 @@
# Add project specific ProGuard rules here.
# You can control the set of applied configuration files using the
# proguardFiles setting in build.gradle.
#
# For more details, see
# http://developer.android.com/guide/developing/tools/proguard.html
# If your project uses WebView with JS, uncomment the following
# and specify the fully qualified class name to the JavaScript interface
# class:
#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
# public *;
#}
# Uncomment this to preserve the line number information for
# debugging stack traces.
#-keepattributes SourceFile,LineNumberTable
# If you keep the line number information, uncomment this to
# hide the original source file name.
#-renamesourcefileattribute SourceFile

View file

@ -0,0 +1,4 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
</manifest>

View file

@ -0,0 +1,26 @@
package com.github.damontecres.wholphin.mpv
/**
* Stubs out MPVLib in wholphin-mpv
*/
object MPVLib {
fun setPropertyString(
key: String,
value: String,
): Unit = throw MpvStubException()
fun setPropertyInt(
key: String,
value: Int,
): Unit = throw MpvStubException()
fun setPropertyDouble(
key: String,
value: Double,
): Unit = throw MpvStubException()
fun setPropertyBoolean(
key: String,
value: Boolean,
): Unit = throw MpvStubException()
}

View file

@ -0,0 +1,22 @@
package com.github.damontecres.wholphin.mpv
import android.content.Context
import android.os.Looper
import androidx.annotation.OptIn
import androidx.media3.common.SimpleBasePlayer
import androidx.media3.common.util.UnstableApi
import kotlin.time.Duration
/**
* Stubs out MpvPlayer in wholphin-mpv
*/
@OptIn(UnstableApi::class)
class MpvPlayer(
private val context: Context,
private val enableHardwareDecoding: Boolean,
private val useGpuNext: Boolean,
) : SimpleBasePlayer(Looper.getMainLooper()) {
override fun getState(): State = throw MpvStubException()
var subtitleDelay: Duration = throw MpvStubException()
}

View file

@ -0,0 +1,3 @@
package com.github.damontecres.wholphin.mpv
class MpvStubException : Exception("This build is using wholphin-mpv-stub which does not implement actual functionality")