mirror of
https://github.com/JustinZeus/Wholphin.git
synced 2026-07-08 23:51:21 +02:00
WIP
This commit is contained in:
parent
77c787b51f
commit
1ee834d42b
37 changed files with 2322 additions and 18 deletions
7
.gitignore
vendored
7
.gitignore
vendored
|
|
@ -53,3 +53,10 @@ app/libs/
|
|||
ffmpeg_decoder/
|
||||
app/release/
|
||||
app/debug/
|
||||
|
||||
# mpv
|
||||
app/src/main/obj/
|
||||
app/src/main/libs/
|
||||
app/src/main/jnilibs/
|
||||
scripts/mpv/deps/
|
||||
scripts/mpv/prefix/
|
||||
|
|
|
|||
|
|
@ -134,15 +134,25 @@ android {
|
|||
}
|
||||
}
|
||||
|
||||
applicationVariants.all {
|
||||
val variant = this
|
||||
variant.outputs
|
||||
.map { it as com.android.build.gradle.internal.api.BaseVariantOutputImpl }
|
||||
.forEach { output ->
|
||||
val outputFileName =
|
||||
"Wholphin-${variant.baseName}-${variant.versionName}-${variant.versionCode}.apk"
|
||||
output.outputFileName = outputFileName
|
||||
}
|
||||
// applicationVariants.all {
|
||||
// val variant = this
|
||||
// variant.outputs
|
||||
// .map { it as com.android.build.gradle.internal.api.BaseVariantOutputImpl }
|
||||
// .forEach { output ->
|
||||
// val outputFileName =
|
||||
// "Wholphin-${variant.baseName}-${variant.versionName}-${variant.versionCode}.apk"
|
||||
// output.outputFileName = outputFileName
|
||||
// }
|
||||
// }
|
||||
}
|
||||
|
||||
splits {
|
||||
abi {
|
||||
isEnable = true
|
||||
reset()
|
||||
include("arm64-v8a")
|
||||
// include("armeabi-v7a") // TODO
|
||||
isUniversalApk = false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -175,6 +185,25 @@ aboutLibraries {
|
|||
duplicationRule = DuplicateRule.SIMPLE
|
||||
}
|
||||
}
|
||||
|
||||
// Map versionCode so each ABI gets a different one
|
||||
// e.g. x86 with version 21 gets a versionCode of 3021
|
||||
// val abiCodes = mapOf("armeabi-v7a" to 1, "arm64-v8a" to 2, "x86" to 3, "x86_64" to 4)
|
||||
// val universalBase = 8000
|
||||
// android.applicationVariants.all { variant ->
|
||||
// variant.outputs.forEach { output ->
|
||||
// val base = output.filters.firstOrNull { it.filterType==VariantOutput.ABI }
|
||||
// // val base = abiCodes.get(output.getFilter(VariantOutput.ABI))
|
||||
// // universal APK just gets a constant added to it
|
||||
// if (base != null) {
|
||||
// output.versionCodeOverride = base * 1000 + variant.versionCode
|
||||
// } else {
|
||||
// output.versionCodeOverride = universalBase + variant.versionCode
|
||||
// }
|
||||
// }
|
||||
// true
|
||||
// }
|
||||
|
||||
dependencies {
|
||||
implementation(libs.androidx.core.ktx)
|
||||
implementation(libs.androidx.appcompat)
|
||||
|
|
|
|||
|
|
@ -236,7 +236,7 @@ fun PlaybackPage(
|
|||
surfaceType = SURFACE_TYPE_SURFACE_VIEW,
|
||||
modifier = scaledModifier,
|
||||
)
|
||||
if (presentationState.coverSurface) {
|
||||
if (false && presentationState.coverSurface) {
|
||||
Box(
|
||||
Modifier
|
||||
.matchParentSize()
|
||||
|
|
|
|||
|
|
@ -18,7 +18,6 @@ import androidx.media3.common.TrackSelectionOverride
|
|||
import androidx.media3.common.Tracks
|
||||
import androidx.media3.common.util.UnstableApi
|
||||
import androidx.media3.exoplayer.DefaultRenderersFactory
|
||||
import androidx.media3.exoplayer.ExoPlayer
|
||||
import com.github.damontecres.wholphin.R
|
||||
import com.github.damontecres.wholphin.data.ItemPlaybackDao
|
||||
import com.github.damontecres.wholphin.data.ItemPlaybackRepository
|
||||
|
|
@ -53,6 +52,7 @@ import com.github.damontecres.wholphin.util.TrackActivityPlaybackListener
|
|||
import com.github.damontecres.wholphin.util.TrackSupport
|
||||
import com.github.damontecres.wholphin.util.checkForSupport
|
||||
import com.github.damontecres.wholphin.util.formatDateTime
|
||||
import com.github.damontecres.wholphin.util.mpv.MpvPlayer
|
||||
import com.github.damontecres.wholphin.util.seasonEpisodePadded
|
||||
import com.github.damontecres.wholphin.util.subtitleMimeTypes
|
||||
import com.github.damontecres.wholphin.util.supportItemKinds
|
||||
|
|
@ -131,13 +131,14 @@ class PlaybackViewModel
|
|||
MediaExtensionStatus.MES_DISABLED -> DefaultRenderersFactory.EXTENSION_RENDERER_MODE_OFF
|
||||
else -> DefaultRenderersFactory.EXTENSION_RENDERER_MODE_ON
|
||||
}
|
||||
ExoPlayer
|
||||
.Builder(context)
|
||||
.setRenderersFactory(
|
||||
DefaultRenderersFactory(context)
|
||||
.setEnableDecoderFallback(true)
|
||||
.setExtensionRendererMode(rendererMode),
|
||||
).build()
|
||||
// ExoPlayer
|
||||
// .Builder(context)
|
||||
// .setRenderersFactory(
|
||||
// DefaultRenderersFactory(context)
|
||||
// .setEnableDecoderFallback(true)
|
||||
// .setExtensionRendererMode(rendererMode),
|
||||
// ).build()
|
||||
MpvPlayer(context)
|
||||
.apply {
|
||||
playWhenReady = true
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,264 @@
|
|||
package com.github.damontecres.wholphin.util.mpv
|
||||
|
||||
import android.content.Context
|
||||
import android.graphics.Bitmap
|
||||
import android.view.Surface
|
||||
|
||||
// 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 = mutableListOf<EventObserver>()
|
||||
|
||||
@JvmStatic
|
||||
fun addObserver(o: EventObserver) {
|
||||
synchronized(observers) {
|
||||
observers.add(o)
|
||||
}
|
||||
}
|
||||
|
||||
@JvmStatic
|
||||
fun removeObserver(o: EventObserver) {
|
||||
synchronized(observers) {
|
||||
observers.remove(o)
|
||||
}
|
||||
}
|
||||
|
||||
@JvmStatic
|
||||
fun eventProperty(
|
||||
property: String,
|
||||
value: Long,
|
||||
) {
|
||||
synchronized(observers) {
|
||||
for (o in observers) {
|
||||
o.eventProperty(property, value)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@JvmStatic
|
||||
fun eventProperty(
|
||||
property: String,
|
||||
value: Boolean,
|
||||
) {
|
||||
synchronized(observers) {
|
||||
for (o in observers) {
|
||||
o.eventProperty(property, value)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@JvmStatic
|
||||
fun eventProperty(
|
||||
property: String,
|
||||
value: Double,
|
||||
) {
|
||||
synchronized(observers) {
|
||||
for (o in observers) {
|
||||
o.eventProperty(property, value)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@JvmStatic
|
||||
fun eventProperty(
|
||||
property: String,
|
||||
value: String,
|
||||
) {
|
||||
synchronized(observers) {
|
||||
for (o in observers) {
|
||||
o.eventProperty(property, value)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@JvmStatic
|
||||
fun eventProperty(property: String) {
|
||||
synchronized(observers) {
|
||||
for (o in observers) {
|
||||
o.eventProperty(property)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@JvmStatic
|
||||
fun event(eventId: Int) {
|
||||
synchronized(observers) {
|
||||
for (o in observers) {
|
||||
o.event(eventId)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private val log_observers = mutableListOf<LogObserver>()
|
||||
|
||||
@JvmStatic
|
||||
fun addLogObserver(o: LogObserver) {
|
||||
synchronized(log_observers) {
|
||||
log_observers.add(o)
|
||||
}
|
||||
}
|
||||
|
||||
@JvmStatic
|
||||
fun removeLogObserver(o: LogObserver) {
|
||||
synchronized(log_observers) {
|
||||
log_observers.remove(o)
|
||||
}
|
||||
}
|
||||
|
||||
@JvmStatic
|
||||
fun logMessage(
|
||||
prefix: String,
|
||||
level: Int,
|
||||
text: String,
|
||||
) {
|
||||
synchronized(log_observers) {
|
||||
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)
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,392 @@
|
|||
package com.github.damontecres.wholphin.util.mpv
|
||||
|
||||
import android.content.Context
|
||||
import android.os.Build
|
||||
import android.os.Looper
|
||||
import android.view.Surface
|
||||
import android.view.SurfaceHolder
|
||||
import android.view.SurfaceView
|
||||
import android.view.TextureView
|
||||
import androidx.annotation.OptIn
|
||||
import androidx.media3.common.AudioAttributes
|
||||
import androidx.media3.common.BasePlayer
|
||||
import androidx.media3.common.C
|
||||
import androidx.media3.common.DeviceInfo
|
||||
import androidx.media3.common.MediaItem
|
||||
import androidx.media3.common.MediaMetadata
|
||||
import androidx.media3.common.PlaybackException
|
||||
import androidx.media3.common.PlaybackParameters
|
||||
import androidx.media3.common.Player
|
||||
import androidx.media3.common.Timeline
|
||||
import androidx.media3.common.TrackSelectionParameters
|
||||
import androidx.media3.common.Tracks
|
||||
import androidx.media3.common.VideoSize
|
||||
import androidx.media3.common.text.CueGroup
|
||||
import androidx.media3.common.util.Size
|
||||
import androidx.media3.common.util.UnstableApi
|
||||
import androidx.media3.common.util.Util
|
||||
import kotlin.time.Duration.Companion.milliseconds
|
||||
|
||||
@OptIn(UnstableApi::class)
|
||||
class MpvPlayer(
|
||||
private val context: Context,
|
||||
) : BasePlayer() {
|
||||
private var surface: Surface? = null
|
||||
private val listeners = mutableListOf<Player.Listener>()
|
||||
private val looper = Util.getCurrentOrMainLooper()
|
||||
private val availableCommands: Player.Commands
|
||||
private lateinit var surfaceSize: Size
|
||||
|
||||
private var mediaItem: MediaItem? = null
|
||||
|
||||
init {
|
||||
MPVLib.create(context)
|
||||
MPVLib.init()
|
||||
MPVLib.setOptionString("hwdec", "mediacodec,mediacodec-copy")
|
||||
MPVLib.setOptionString("gpu-context", "android")
|
||||
MPVLib.setOptionString("opengl-es", "yes")
|
||||
MPVLib.setOptionString("hwdec-codecs", "h264,hevc,mpeg4,mpeg2video,vp8,vp9,av1")
|
||||
val cacheMegs = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O_MR1) 64 else 32
|
||||
MPVLib.setOptionString("demuxer-max-bytes", "${cacheMegs * 1024 * 1024}")
|
||||
MPVLib.setOptionString("demuxer-max-back-bytes", "${cacheMegs * 1024 * 1024}")
|
||||
|
||||
MPVLib.setOptionString("force-window", "no")
|
||||
// need to idle at least once for playFile() logic to work
|
||||
MPVLib.setOptionString("idle", "once")
|
||||
|
||||
availableCommands =
|
||||
Player.Commands
|
||||
.Builder()
|
||||
.addAll(
|
||||
COMMAND_PLAY_PAUSE,
|
||||
COMMAND_PREPARE,
|
||||
COMMAND_STOP,
|
||||
COMMAND_SET_SPEED_AND_PITCH,
|
||||
COMMAND_SET_SHUFFLE_MODE,
|
||||
COMMAND_SET_REPEAT_MODE,
|
||||
COMMAND_GET_CURRENT_MEDIA_ITEM,
|
||||
COMMAND_GET_TIMELINE,
|
||||
COMMAND_GET_METADATA,
|
||||
// COMMAND_SET_PLAYLIST_METADATA,
|
||||
COMMAND_SET_MEDIA_ITEM,
|
||||
// COMMAND_CHANGE_MEDIA_ITEMS,
|
||||
COMMAND_GET_TRACKS,
|
||||
// COMMAND_GET_AUDIO_ATTRIBUTES,
|
||||
// COMMAND_SET_AUDIO_ATTRIBUTES,
|
||||
// COMMAND_GET_VOLUME,
|
||||
// COMMAND_SET_VOLUME,
|
||||
COMMAND_SET_VIDEO_SURFACE,
|
||||
// COMMAND_GET_TEXT,
|
||||
COMMAND_RELEASE,
|
||||
).build()
|
||||
}
|
||||
|
||||
override fun getApplicationLooper(): Looper = looper
|
||||
|
||||
override fun addListener(listener: Player.Listener) {
|
||||
listeners.add(listener)
|
||||
}
|
||||
|
||||
override fun removeListener(listener: Player.Listener) {
|
||||
listeners.remove(listener)
|
||||
}
|
||||
|
||||
override fun setMediaItems(
|
||||
mediaItems: List<MediaItem>,
|
||||
resetPosition: Boolean,
|
||||
) {
|
||||
mediaItems.firstOrNull()?.let {
|
||||
mediaItem = it
|
||||
}
|
||||
}
|
||||
|
||||
override fun setMediaItems(
|
||||
mediaItems: List<MediaItem>,
|
||||
startIndex: Int,
|
||||
startPositionMs: Long,
|
||||
) {
|
||||
setMediaItems(mediaItems.subList(startIndex, mediaItems.size), false)
|
||||
seekTo(startPositionMs)
|
||||
}
|
||||
|
||||
override fun addMediaItems(
|
||||
index: Int,
|
||||
mediaItems: List<MediaItem>,
|
||||
): Unit = throw UnsupportedOperationException()
|
||||
|
||||
override fun moveMediaItems(
|
||||
fromIndex: Int,
|
||||
toIndex: Int,
|
||||
newIndex: Int,
|
||||
): Unit = throw UnsupportedOperationException()
|
||||
|
||||
override fun replaceMediaItems(
|
||||
fromIndex: Int,
|
||||
toIndex: Int,
|
||||
mediaItems: List<MediaItem>,
|
||||
): Unit = throw UnsupportedOperationException()
|
||||
|
||||
override fun removeMediaItems(
|
||||
fromIndex: Int,
|
||||
toIndex: Int,
|
||||
): Unit = throw UnsupportedOperationException()
|
||||
|
||||
override fun getAvailableCommands(): Player.Commands = availableCommands
|
||||
|
||||
override fun prepare() {
|
||||
}
|
||||
|
||||
override fun getPlaybackState(): Int {
|
||||
val state = STATE_READY
|
||||
return state
|
||||
}
|
||||
|
||||
override fun getPlaybackSuppressionReason(): Int = Player.PLAYBACK_SUPPRESSION_REASON_NONE
|
||||
|
||||
override fun getPlayerError(): PlaybackException? {
|
||||
TODO("Not yet implemented")
|
||||
}
|
||||
|
||||
override fun setPlayWhenReady(playWhenReady: Boolean) {
|
||||
if (playWhenReady) {
|
||||
} else {
|
||||
MPVLib.setPropertyBoolean("pause", true)
|
||||
}
|
||||
}
|
||||
|
||||
override fun getPlayWhenReady(): Boolean {
|
||||
val isPaused = MPVLib.getPropertyBoolean("pause")!!
|
||||
return !isPaused
|
||||
}
|
||||
|
||||
override fun setRepeatMode(repeatMode: Int) {
|
||||
}
|
||||
|
||||
override fun getRepeatMode(): Int = Player.REPEAT_MODE_OFF
|
||||
|
||||
override fun setShuffleModeEnabled(shuffleModeEnabled: Boolean) {
|
||||
}
|
||||
|
||||
override fun getShuffleModeEnabled(): Boolean = false
|
||||
|
||||
override fun isLoading(): Boolean = false
|
||||
|
||||
override fun getSeekBackIncrement(): Long = 10_000
|
||||
|
||||
override fun getSeekForwardIncrement(): Long = 30_000
|
||||
|
||||
override fun getMaxSeekToPreviousPosition(): Long = 10_000
|
||||
|
||||
override fun setPlaybackParameters(playbackParameters: PlaybackParameters) {
|
||||
// TODO
|
||||
}
|
||||
|
||||
override fun getPlaybackParameters(): PlaybackParameters {
|
||||
// TODO
|
||||
return PlaybackParameters.DEFAULT
|
||||
}
|
||||
|
||||
override fun stop() {
|
||||
pause()
|
||||
// MPVLib.setPropertyString("vo", "gpu")
|
||||
}
|
||||
|
||||
override fun release() {
|
||||
MPVLib.destroy()
|
||||
}
|
||||
|
||||
override fun getCurrentTracks(): Tracks {
|
||||
// TODO
|
||||
return Tracks.EMPTY
|
||||
}
|
||||
|
||||
override fun getTrackSelectionParameters(): TrackSelectionParameters {
|
||||
// TODO
|
||||
return TrackSelectionParameters.Builder().build()
|
||||
}
|
||||
|
||||
override fun setTrackSelectionParameters(parameters: TrackSelectionParameters) {
|
||||
// TODO
|
||||
}
|
||||
|
||||
override fun getMediaMetadata(): MediaMetadata = mediaItem!!.mediaMetadata
|
||||
|
||||
override fun getPlaylistMetadata(): MediaMetadata = MediaMetadata.EMPTY
|
||||
|
||||
override fun setPlaylistMetadata(mediaMetadata: MediaMetadata): Unit = throw UnsupportedOperationException()
|
||||
|
||||
override fun getCurrentTimeline(): Timeline {
|
||||
// TODO
|
||||
return Timeline.EMPTY
|
||||
}
|
||||
|
||||
override fun getCurrentPeriodIndex(): Int {
|
||||
// TODO
|
||||
return 0
|
||||
}
|
||||
|
||||
override fun getCurrentMediaItemIndex(): Int = 0
|
||||
|
||||
override fun getDuration(): Long {
|
||||
val duration = MPVLib.getPropertyDouble("duration")!!.milliseconds.inWholeMilliseconds
|
||||
return duration
|
||||
}
|
||||
|
||||
override fun getCurrentPosition(): Long {
|
||||
val position = MPVLib.getPropertyDouble("time-pos/full")?.milliseconds?.inWholeMilliseconds ?: 0
|
||||
return position
|
||||
}
|
||||
|
||||
override fun getBufferedPosition(): Long {
|
||||
// TODO
|
||||
return currentPosition
|
||||
}
|
||||
|
||||
override fun getTotalBufferedDuration(): Long = bufferedPosition
|
||||
|
||||
override fun isPlayingAd(): Boolean = false
|
||||
|
||||
override fun getCurrentAdGroupIndex(): Int = C.INDEX_UNSET
|
||||
|
||||
override fun getCurrentAdIndexInAdGroup(): Int = C.INDEX_UNSET
|
||||
|
||||
override fun getContentPosition(): Long = currentPosition
|
||||
|
||||
override fun getContentBufferedPosition(): Long = bufferedPosition
|
||||
|
||||
override fun getAudioAttributes(): AudioAttributes {
|
||||
TODO("Not yet implemented")
|
||||
}
|
||||
|
||||
override fun setVolume(volume: Float) {
|
||||
}
|
||||
|
||||
override fun getVolume(): Float = 1f
|
||||
|
||||
override fun clearVideoSurface(): Unit = throw UnsupportedOperationException()
|
||||
|
||||
override fun clearVideoSurface(surface: Surface?): Unit = throw UnsupportedOperationException()
|
||||
|
||||
override fun setVideoSurface(surface: Surface?): Unit = throw UnsupportedOperationException()
|
||||
|
||||
override fun setVideoSurfaceHolder(surfaceHolder: SurfaceHolder?): Unit = throw UnsupportedOperationException()
|
||||
|
||||
override fun clearVideoSurfaceHolder(surfaceHolder: SurfaceHolder?): Unit = throw UnsupportedOperationException()
|
||||
|
||||
override fun setVideoSurfaceView(surfaceView: SurfaceView?) {
|
||||
val surface = surfaceView?.holder?.surface
|
||||
val newSurfaceSize = if (surface == null) 0 else C.LENGTH_UNSET
|
||||
this.surfaceSize = if (surface == null) Size(0, 0) else Size.UNKNOWN
|
||||
if (surface != null) {
|
||||
this.surface = surface
|
||||
MPVLib.attachSurface(surface)
|
||||
MPVLib.setOptionString("force-window", "yes")
|
||||
|
||||
val url = mediaItem!!.localConfiguration?.uri.toString()
|
||||
MPVLib.command(arrayOf("loadfile", url))
|
||||
} else {
|
||||
clearVideoSurfaceView(null)
|
||||
}
|
||||
}
|
||||
|
||||
override fun clearVideoSurfaceView(surfaceView: SurfaceView?) {
|
||||
MPVLib.detachSurface()
|
||||
MPVLib.setPropertyString("vo", "null")
|
||||
MPVLib.setPropertyString("force-window", "no")
|
||||
mediaItem = null
|
||||
}
|
||||
|
||||
override fun setVideoTextureView(textureView: TextureView?): Unit = throw UnsupportedOperationException()
|
||||
|
||||
override fun clearVideoTextureView(textureView: TextureView?): Unit = throw UnsupportedOperationException()
|
||||
|
||||
override fun getVideoSize(): VideoSize {
|
||||
val width = MPVLib.getPropertyInt("width")
|
||||
val height = MPVLib.getPropertyInt("height")
|
||||
return if (width != null && height != null) {
|
||||
VideoSize(width, height)
|
||||
} else {
|
||||
VideoSize.UNKNOWN
|
||||
}
|
||||
}
|
||||
|
||||
override fun getSurfaceSize(): Size {
|
||||
TODO("Not yet implemented")
|
||||
}
|
||||
|
||||
override fun getCurrentCues(): CueGroup = CueGroup.EMPTY_TIME_ZERO
|
||||
|
||||
override fun getDeviceInfo(): DeviceInfo {
|
||||
// TODO
|
||||
return DeviceInfo.Builder(DeviceInfo.PLAYBACK_TYPE_REMOTE).build()
|
||||
}
|
||||
|
||||
override fun getDeviceVolume(): Int {
|
||||
TODO("Not yet implemented")
|
||||
}
|
||||
|
||||
override fun isDeviceMuted(): Boolean {
|
||||
TODO("Not yet implemented")
|
||||
}
|
||||
|
||||
@Deprecated("Deprecated in Java")
|
||||
override fun setDeviceVolume(volume: Int) {
|
||||
TODO("Not yet implemented")
|
||||
}
|
||||
|
||||
override fun setDeviceVolume(
|
||||
volume: Int,
|
||||
flags: Int,
|
||||
) {
|
||||
TODO("Not yet implemented")
|
||||
}
|
||||
|
||||
@Deprecated("Deprecated in Java")
|
||||
override fun increaseDeviceVolume() {
|
||||
TODO("Not yet implemented")
|
||||
}
|
||||
|
||||
override fun increaseDeviceVolume(flags: Int) {
|
||||
TODO("Not yet implemented")
|
||||
}
|
||||
|
||||
@Deprecated("Deprecated in Java")
|
||||
override fun decreaseDeviceVolume() {
|
||||
TODO("Not yet implemented")
|
||||
}
|
||||
|
||||
override fun decreaseDeviceVolume(flags: Int) {
|
||||
TODO("Not yet implemented")
|
||||
}
|
||||
|
||||
@Deprecated("Deprecated in Java")
|
||||
override fun setDeviceMuted(muted: Boolean) {
|
||||
TODO("Not yet implemented")
|
||||
}
|
||||
|
||||
override fun setDeviceMuted(
|
||||
muted: Boolean,
|
||||
flags: Int,
|
||||
) {
|
||||
TODO("Not yet implemented")
|
||||
}
|
||||
|
||||
override fun setAudioAttributes(
|
||||
audioAttributes: AudioAttributes,
|
||||
handleAudioFocus: Boolean,
|
||||
) {
|
||||
TODO("Not yet implemented")
|
||||
}
|
||||
|
||||
override fun seekTo(
|
||||
mediaItemIndex: Int,
|
||||
positionMs: Long,
|
||||
seekCommand: Int,
|
||||
isRepeatingCurrentItem: Boolean,
|
||||
) {
|
||||
if (mediaItemIndex == C.INDEX_UNSET) {
|
||||
return
|
||||
}
|
||||
MPVLib.setPropertyDouble("time-pos", positionMs.toDouble())
|
||||
}
|
||||
}
|
||||
82
app/src/main/jni/Android.mk
Normal file
82
app/src/main/jni/Android.mk
Normal file
|
|
@ -0,0 +1,82 @@
|
|||
LOCAL_PATH:= $(call my-dir)
|
||||
|
||||
ifeq ($(TARGET_ARCH_ABI),armeabi-v7a)
|
||||
PREFIX = $(PREFIX32)
|
||||
endif
|
||||
ifeq ($(TARGET_ARCH_ABI),arm64-v8a)
|
||||
PREFIX = $(PREFIX64)
|
||||
endif
|
||||
ifeq ($(TARGET_ARCH_ABI),x86_64)
|
||||
PREFIX = $(PREFIX_X64)
|
||||
endif
|
||||
ifeq ($(TARGET_ARCH_ABI),x86)
|
||||
PREFIX = $(PREFIX_X86)
|
||||
endif
|
||||
|
||||
include $(CLEAR_VARS)
|
||||
LOCAL_MODULE := libswresample
|
||||
LOCAL_SRC_FILES := $(PREFIX)/lib/$(LOCAL_MODULE).so
|
||||
include $(PREBUILT_SHARED_LIBRARY)
|
||||
|
||||
include $(CLEAR_VARS)
|
||||
LOCAL_MODULE := libpostproc
|
||||
LOCAL_SRC_FILES := $(PREFIX)/lib/$(LOCAL_MODULE).so
|
||||
# only include if library file exists
|
||||
ifneq (,$(wildcard $(LOCAL_SRC_FILES)))
|
||||
include $(PREBUILT_SHARED_LIBRARY)
|
||||
endif
|
||||
|
||||
include $(CLEAR_VARS)
|
||||
LOCAL_MODULE := libavutil
|
||||
LOCAL_SRC_FILES := $(PREFIX)/lib/$(LOCAL_MODULE).so
|
||||
include $(PREBUILT_SHARED_LIBRARY)
|
||||
|
||||
include $(CLEAR_VARS)
|
||||
LOCAL_MODULE := libavcodec
|
||||
LOCAL_SRC_FILES := $(PREFIX)/lib/$(LOCAL_MODULE).so
|
||||
include $(PREBUILT_SHARED_LIBRARY)
|
||||
|
||||
include $(CLEAR_VARS)
|
||||
LOCAL_MODULE := libavformat
|
||||
LOCAL_SRC_FILES := $(PREFIX)/lib/$(LOCAL_MODULE).so
|
||||
include $(PREBUILT_SHARED_LIBRARY)
|
||||
|
||||
include $(CLEAR_VARS)
|
||||
LOCAL_MODULE := libswscale
|
||||
LOCAL_SRC_FILES := $(PREFIX)/lib/$(LOCAL_MODULE).so
|
||||
include $(PREBUILT_SHARED_LIBRARY)
|
||||
|
||||
include $(CLEAR_VARS)
|
||||
LOCAL_MODULE := libavfilter
|
||||
LOCAL_SRC_FILES := $(PREFIX)/lib/$(LOCAL_MODULE).so
|
||||
include $(PREBUILT_SHARED_LIBRARY)
|
||||
|
||||
include $(CLEAR_VARS)
|
||||
LOCAL_MODULE := libavdevice
|
||||
LOCAL_SRC_FILES := $(PREFIX)/lib/$(LOCAL_MODULE).so
|
||||
LOCAL_EXPORT_C_INCLUDES := $(PREFIX)/include
|
||||
include $(PREBUILT_SHARED_LIBRARY)
|
||||
|
||||
include $(CLEAR_VARS)
|
||||
LOCAL_MODULE := libmpv
|
||||
LOCAL_SRC_FILES := $(PREFIX)/lib/libmpv.so
|
||||
LOCAL_EXPORT_C_INCLUDES := $(PREFIX)/include
|
||||
include $(PREBUILT_SHARED_LIBRARY)
|
||||
|
||||
include $(CLEAR_VARS)
|
||||
|
||||
LOCAL_MODULE := libplayer
|
||||
LOCAL_CFLAGS := -Werror
|
||||
LOCAL_CPPFLAGS += -std=c++11
|
||||
LOCAL_SRC_FILES := \
|
||||
main.cpp \
|
||||
render.cpp \
|
||||
log.cpp \
|
||||
jni_utils.cpp \
|
||||
property.cpp \
|
||||
event.cpp \
|
||||
thumbnail.cpp
|
||||
LOCAL_LDLIBS := -llog -lGLESv3 -lEGL -latomic
|
||||
LOCAL_SHARED_LIBRARIES := swscale avcodec mpv
|
||||
|
||||
include $(BUILD_SHARED_LIBRARY)
|
||||
17
app/src/main/jni/Application.mk
Normal file
17
app/src/main/jni/Application.mk
Normal file
|
|
@ -0,0 +1,17 @@
|
|||
APP_ABI :=
|
||||
ifneq ($(PREFIX32),)
|
||||
APP_ABI += armeabi-v7a
|
||||
endif
|
||||
ifneq ($(PREFIX64),)
|
||||
APP_ABI += arm64-v8a
|
||||
endif
|
||||
ifneq ($(PREFIX_X64),)
|
||||
APP_ABI += x86_64
|
||||
endif
|
||||
ifneq ($(PREFIX_X86),)
|
||||
APP_ABI += x86
|
||||
endif
|
||||
|
||||
APP_PLATFORM := android-21
|
||||
APP_STL := c++_shared
|
||||
APP_SUPPORT_FLEXIBLE_PAGE_SIZES := true
|
||||
111
app/src/main/jni/event.cpp
Normal file
111
app/src/main/jni/event.cpp
Normal file
|
|
@ -0,0 +1,111 @@
|
|||
#include <jni.h>
|
||||
|
||||
#include <mpv/client.h>
|
||||
|
||||
#include "globals.h"
|
||||
#include "jni_utils.h"
|
||||
#include "log.h"
|
||||
|
||||
static void sendPropertyUpdateToJava(JNIEnv *env, mpv_event_property *prop)
|
||||
{
|
||||
jstring jprop = env->NewStringUTF(prop->name);
|
||||
jstring jvalue = NULL;
|
||||
switch (prop->format) {
|
||||
case MPV_FORMAT_NONE:
|
||||
env->CallStaticVoidMethod(mpv_MPVLib, mpv_MPVLib_eventProperty_S, jprop);
|
||||
break;
|
||||
case MPV_FORMAT_FLAG:
|
||||
env->CallStaticVoidMethod(mpv_MPVLib, mpv_MPVLib_eventProperty_Sb, jprop,
|
||||
(jboolean) (*(int*)prop->data != 0));
|
||||
break;
|
||||
case MPV_FORMAT_INT64:
|
||||
env->CallStaticVoidMethod(mpv_MPVLib, mpv_MPVLib_eventProperty_Sl, jprop,
|
||||
(jlong) *(int64_t*)prop->data);
|
||||
break;
|
||||
case MPV_FORMAT_DOUBLE:
|
||||
env->CallStaticVoidMethod(mpv_MPVLib, mpv_MPVLib_eventProperty_Sd, jprop,
|
||||
(jdouble) *(double*)prop->data);
|
||||
break;
|
||||
case MPV_FORMAT_STRING:
|
||||
jvalue = env->NewStringUTF(*(const char**)prop->data);
|
||||
env->CallStaticVoidMethod(mpv_MPVLib, mpv_MPVLib_eventProperty_SS, jprop, jvalue);
|
||||
break;
|
||||
default:
|
||||
ALOGV("sendPropertyUpdateToJava: Unknown property update format received in callback: %d!", prop->format);
|
||||
break;
|
||||
}
|
||||
if (jprop)
|
||||
env->DeleteLocalRef(jprop);
|
||||
if (jvalue)
|
||||
env->DeleteLocalRef(jvalue);
|
||||
}
|
||||
|
||||
static void sendEventToJava(JNIEnv *env, int event)
|
||||
{
|
||||
env->CallStaticVoidMethod(mpv_MPVLib, mpv_MPVLib_event, event);
|
||||
}
|
||||
|
||||
static void sendLogMessageToJava(JNIEnv *env, mpv_event_log_message *msg)
|
||||
{
|
||||
// filter the most obvious cases of invalid utf-8, since Java would choke on it
|
||||
const auto invalid_utf8 = [] (unsigned char c) {
|
||||
return c == 0xc0 || c == 0xc1 || c >= 0xf5;
|
||||
};
|
||||
for (int i = 0; msg->text[i]; i++) {
|
||||
if (invalid_utf8(static_cast<unsigned char>(msg->text[i])))
|
||||
return;
|
||||
}
|
||||
|
||||
jstring jprefix = env->NewStringUTF(msg->prefix);
|
||||
jstring jtext = env->NewStringUTF(msg->text);
|
||||
|
||||
env->CallStaticVoidMethod(mpv_MPVLib, mpv_MPVLib_logMessage_SiS,
|
||||
jprefix, (jint) msg->log_level, jtext);
|
||||
|
||||
if (jprefix)
|
||||
env->DeleteLocalRef(jprefix);
|
||||
if (jtext)
|
||||
env->DeleteLocalRef(jtext);
|
||||
}
|
||||
|
||||
void *event_thread(void *arg)
|
||||
{
|
||||
JNIEnv *env = NULL;
|
||||
acquire_jni_env(g_vm, &env);
|
||||
if (!env)
|
||||
die("failed to acquire java env");
|
||||
|
||||
while (1) {
|
||||
mpv_event *mp_event;
|
||||
mpv_event_property *mp_property = NULL;
|
||||
mpv_event_log_message *msg = NULL;
|
||||
|
||||
mp_event = mpv_wait_event(g_mpv, -1.0);
|
||||
|
||||
if (g_event_thread_request_exit)
|
||||
break;
|
||||
|
||||
if (mp_event->event_id == MPV_EVENT_NONE)
|
||||
continue;
|
||||
|
||||
switch (mp_event->event_id) {
|
||||
case MPV_EVENT_LOG_MESSAGE:
|
||||
msg = (mpv_event_log_message*)mp_event->data;
|
||||
ALOGV("[%s:%s] %s", msg->prefix, msg->level, msg->text);
|
||||
sendLogMessageToJava(env, msg);
|
||||
break;
|
||||
case MPV_EVENT_PROPERTY_CHANGE:
|
||||
mp_property = (mpv_event_property*)mp_event->data;
|
||||
sendPropertyUpdateToJava(env, mp_property);
|
||||
break;
|
||||
default:
|
||||
ALOGV("event: %s\n", mpv_event_name(mp_event->event_id));
|
||||
sendEventToJava(env, mp_event->event_id);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
g_vm->DetachCurrentThread();
|
||||
|
||||
return NULL;
|
||||
}
|
||||
3
app/src/main/jni/event.h
Normal file
3
app/src/main/jni/event.h
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
#pragma once
|
||||
|
||||
void *event_thread(void *arg);
|
||||
7
app/src/main/jni/globals.h
Normal file
7
app/src/main/jni/globals.h
Normal file
|
|
@ -0,0 +1,7 @@
|
|||
#pragma once
|
||||
|
||||
#include <atomic>
|
||||
|
||||
extern JavaVM *g_vm;
|
||||
extern mpv_handle *g_mpv;
|
||||
extern std::atomic<bool> g_event_thread_request_exit;
|
||||
51
app/src/main/jni/jni_utils.cpp
Normal file
51
app/src/main/jni/jni_utils.cpp
Normal file
|
|
@ -0,0 +1,51 @@
|
|||
#define UTIL_EXTERN
|
||||
#include "jni_utils.h"
|
||||
|
||||
#include <jni.h>
|
||||
#include <stdlib.h>
|
||||
|
||||
bool acquire_jni_env(JavaVM *vm, JNIEnv **env)
|
||||
{
|
||||
int ret = vm->GetEnv((void**) env, JNI_VERSION_1_6);
|
||||
if (ret == JNI_EDETACHED)
|
||||
return vm->AttachCurrentThread(env, NULL) == 0;
|
||||
else
|
||||
return ret == JNI_OK;
|
||||
}
|
||||
|
||||
// Apparently it's considered slow to FindClass and GetMethodID every time we need them,
|
||||
// so let's have a nice cache here.
|
||||
|
||||
void init_methods_cache(JNIEnv *env)
|
||||
{
|
||||
static bool methods_initialized = false;
|
||||
if (methods_initialized)
|
||||
return;
|
||||
|
||||
#define FIND_CLASS(name) reinterpret_cast<jclass>(env->NewGlobalRef(env->FindClass(name)))
|
||||
java_Integer = FIND_CLASS("java/lang/Integer");
|
||||
java_Integer_init = env->GetMethodID(java_Integer, "<init>", "(I)V");
|
||||
java_Double = FIND_CLASS("java/lang/Double");
|
||||
java_Double_init = env->GetMethodID(java_Double, "<init>", "(D)V");
|
||||
java_Boolean = FIND_CLASS("java/lang/Boolean");
|
||||
java_Boolean_init = env->GetMethodID(java_Boolean, "<init>", "(Z)V");
|
||||
|
||||
android_graphics_Bitmap = FIND_CLASS("android/graphics/Bitmap");
|
||||
// createBitmap(int[], int, int, android.graphics.Bitmap$Config)
|
||||
android_graphics_Bitmap_createBitmap = env->GetStaticMethodID(android_graphics_Bitmap, "createBitmap", "([IIILandroid/graphics/Bitmap$Config;)Landroid/graphics/Bitmap;");
|
||||
android_graphics_Bitmap_Config = FIND_CLASS("android/graphics/Bitmap$Config");
|
||||
// static final android.graphics.Bitmap$Config ARGB_8888
|
||||
android_graphics_Bitmap_Config_ARGB_8888 = env->GetStaticFieldID(android_graphics_Bitmap_Config, "ARGB_8888", "Landroid/graphics/Bitmap$Config;");
|
||||
|
||||
mpv_MPVLib = FIND_CLASS("com/github/damontecres/wholphin/util/mpv/MPVLib");
|
||||
mpv_MPVLib_eventProperty_S = env->GetStaticMethodID(mpv_MPVLib, "eventProperty", "(Ljava/lang/String;)V"); // eventProperty(String)
|
||||
mpv_MPVLib_eventProperty_Sb = env->GetStaticMethodID(mpv_MPVLib, "eventProperty", "(Ljava/lang/String;Z)V"); // eventProperty(String, boolean)
|
||||
mpv_MPVLib_eventProperty_Sl = env->GetStaticMethodID(mpv_MPVLib, "eventProperty", "(Ljava/lang/String;J)V"); // eventProperty(String, long)
|
||||
mpv_MPVLib_eventProperty_Sd = env->GetStaticMethodID(mpv_MPVLib, "eventProperty", "(Ljava/lang/String;D)V"); // eventProperty(String, double)
|
||||
mpv_MPVLib_eventProperty_SS = env->GetStaticMethodID(mpv_MPVLib, "eventProperty", "(Ljava/lang/String;Ljava/lang/String;)V"); // eventProperty(String, String)
|
||||
mpv_MPVLib_event = env->GetStaticMethodID(mpv_MPVLib, "event", "(I)V"); // event(int)
|
||||
mpv_MPVLib_logMessage_SiS = env->GetStaticMethodID(mpv_MPVLib, "logMessage", "(Ljava/lang/String;ILjava/lang/String;)V"); // logMessage(String, int, String)
|
||||
#undef FIND_CLASS
|
||||
|
||||
methods_initialized = true;
|
||||
}
|
||||
29
app/src/main/jni/jni_utils.h
Normal file
29
app/src/main/jni/jni_utils.h
Normal file
|
|
@ -0,0 +1,29 @@
|
|||
#pragma once
|
||||
|
||||
#include <jni.h>
|
||||
|
||||
#define jni_func_name(name) Java_com_github_damontecres_wholphin_util_mpv_MPVLib_##name
|
||||
#define jni_func(return_type, name, ...) JNIEXPORT return_type JNICALL jni_func_name(name) (JNIEnv *env, jobject obj, ##__VA_ARGS__)
|
||||
|
||||
bool acquire_jni_env(JavaVM *vm, JNIEnv **env);
|
||||
void init_methods_cache(JNIEnv *env);
|
||||
|
||||
#ifndef UTIL_EXTERN
|
||||
#define UTIL_EXTERN extern
|
||||
#endif
|
||||
|
||||
UTIL_EXTERN jclass java_Integer, java_Double, java_Boolean;
|
||||
UTIL_EXTERN jmethodID java_Integer_init, java_Double_init, java_Boolean_init;
|
||||
|
||||
UTIL_EXTERN jclass android_graphics_Bitmap, android_graphics_Bitmap_Config;
|
||||
UTIL_EXTERN jmethodID android_graphics_Bitmap_createBitmap;
|
||||
UTIL_EXTERN jfieldID android_graphics_Bitmap_Config_ARGB_8888;
|
||||
|
||||
UTIL_EXTERN jclass mpv_MPVLib;
|
||||
UTIL_EXTERN jmethodID mpv_MPVLib_eventProperty_S,
|
||||
mpv_MPVLib_eventProperty_Sb,
|
||||
mpv_MPVLib_eventProperty_Sl,
|
||||
mpv_MPVLib_eventProperty_Sd,
|
||||
mpv_MPVLib_eventProperty_SS,
|
||||
mpv_MPVLib_event,
|
||||
mpv_MPVLib_logMessage_SiS;
|
||||
9
app/src/main/jni/log.cpp
Normal file
9
app/src/main/jni/log.cpp
Normal file
|
|
@ -0,0 +1,9 @@
|
|||
#include "log.h"
|
||||
|
||||
#include <stdlib.h>
|
||||
|
||||
void die(const char *msg)
|
||||
{
|
||||
ALOGE("%s", msg);
|
||||
exit(1);
|
||||
}
|
||||
20
app/src/main/jni/log.h
Normal file
20
app/src/main/jni/log.h
Normal file
|
|
@ -0,0 +1,20 @@
|
|||
#pragma once
|
||||
|
||||
#include <android/log.h>
|
||||
|
||||
#define DEBUG 1
|
||||
|
||||
#define LOG_TAG "mpv"
|
||||
#define ALOGE(...) __android_log_print(ANDROID_LOG_ERROR, LOG_TAG, __VA_ARGS__)
|
||||
#if DEBUG
|
||||
#define ALOGV(...) __android_log_print(ANDROID_LOG_VERBOSE, LOG_TAG, __VA_ARGS__)
|
||||
#else
|
||||
#define ALOGV(...) (void)0
|
||||
#endif
|
||||
|
||||
__attribute__((noreturn)) void die(const char *msg);
|
||||
|
||||
#define CHECK_MPV_INIT() do { \
|
||||
if (__builtin_expect(!g_mpv, 0)) \
|
||||
die("libmpv is not initialized"); \
|
||||
} while (0)
|
||||
108
app/src/main/jni/main.cpp
Normal file
108
app/src/main/jni/main.cpp
Normal file
|
|
@ -0,0 +1,108 @@
|
|||
#include <jni.h>
|
||||
#include <stdlib.h>
|
||||
#include <stdio.h>
|
||||
#include <time.h>
|
||||
#include <locale.h>
|
||||
#include <atomic>
|
||||
|
||||
#include <mpv/client.h>
|
||||
|
||||
#include <pthread.h>
|
||||
|
||||
extern "C" {
|
||||
#include <libavcodec/jni.h>
|
||||
}
|
||||
|
||||
#include "log.h"
|
||||
#include "jni_utils.h"
|
||||
#include "event.h"
|
||||
|
||||
#define ARRAYLEN(a) (sizeof(a)/sizeof(a[0]))
|
||||
|
||||
extern "C" {
|
||||
jni_func(void, create, jobject appctx);
|
||||
jni_func(void, init);
|
||||
jni_func(void, destroy);
|
||||
|
||||
jni_func(void, command, jobjectArray jarray);
|
||||
};
|
||||
|
||||
JavaVM *g_vm;
|
||||
mpv_handle *g_mpv;
|
||||
std::atomic<bool> g_event_thread_request_exit(false);
|
||||
|
||||
static pthread_t event_thread_id;
|
||||
|
||||
static void prepare_environment(JNIEnv *env, jobject appctx) {
|
||||
setlocale(LC_NUMERIC, "C");
|
||||
|
||||
if (!env->GetJavaVM(&g_vm) && g_vm)
|
||||
av_jni_set_java_vm(g_vm, NULL);
|
||||
|
||||
jobject global_appctx = env->NewGlobalRef(appctx);
|
||||
if (global_appctx)
|
||||
av_jni_set_android_app_ctx(global_appctx, NULL);
|
||||
|
||||
init_methods_cache(env);
|
||||
}
|
||||
|
||||
jni_func(void, create, jobject appctx) {
|
||||
prepare_environment(env, appctx);
|
||||
|
||||
if (g_mpv)
|
||||
die("mpv is already initialized");
|
||||
|
||||
g_mpv = mpv_create();
|
||||
if (!g_mpv)
|
||||
die("context init failed");
|
||||
|
||||
// use terminal log level but request verbose messages
|
||||
// this way --msg-level can be used to adjust later
|
||||
mpv_request_log_messages(g_mpv, "terminal-default");
|
||||
mpv_set_option_string(g_mpv, "msg-level", "all=v");
|
||||
}
|
||||
|
||||
jni_func(void, init) {
|
||||
if (!g_mpv)
|
||||
die("mpv is not created");
|
||||
|
||||
if (mpv_initialize(g_mpv) < 0)
|
||||
die("mpv init failed");
|
||||
|
||||
g_event_thread_request_exit = false;
|
||||
if (pthread_create(&event_thread_id, NULL, event_thread, NULL) != 0)
|
||||
die("thread create failed");
|
||||
pthread_setname_np(event_thread_id, "event_thread");
|
||||
}
|
||||
|
||||
jni_func(void, destroy) {
|
||||
if (!g_mpv) {
|
||||
ALOGV("mpv destroy called but it's already destroyed");
|
||||
return;
|
||||
}
|
||||
|
||||
// poke event thread and wait for it to exit
|
||||
g_event_thread_request_exit = true;
|
||||
mpv_wakeup(g_mpv);
|
||||
pthread_join(event_thread_id, NULL);
|
||||
|
||||
mpv_terminate_destroy(g_mpv);
|
||||
g_mpv = NULL;
|
||||
}
|
||||
|
||||
jni_func(void, command, jobjectArray jarray) {
|
||||
CHECK_MPV_INIT();
|
||||
|
||||
const char *arguments[128] = {0};
|
||||
int len = env->GetArrayLength(jarray);
|
||||
if (len >= ARRAYLEN(arguments))
|
||||
die("too many command arguments");
|
||||
|
||||
for (int i = 0; i < len; ++i)
|
||||
arguments[i] = env->GetStringUTFChars((jstring)env->GetObjectArrayElement(jarray, i), NULL);
|
||||
|
||||
mpv_command(g_mpv, arguments);
|
||||
|
||||
for (int i = 0; i < len; ++i)
|
||||
env->ReleaseStringUTFChars((jstring)env->GetObjectArrayElement(jarray, i), arguments[i]);
|
||||
}
|
||||
125
app/src/main/jni/property.cpp
Normal file
125
app/src/main/jni/property.cpp
Normal file
|
|
@ -0,0 +1,125 @@
|
|||
#include <jni.h>
|
||||
#include <stdlib.h>
|
||||
|
||||
#include <mpv/client.h>
|
||||
|
||||
#include "jni_utils.h"
|
||||
#include "log.h"
|
||||
#include "globals.h"
|
||||
|
||||
extern "C" {
|
||||
jni_func(jint, setOptionString, jstring option, jstring value);
|
||||
|
||||
jni_func(jobject, getPropertyInt, jstring property);
|
||||
jni_func(void, setPropertyInt, jstring property, jint value);
|
||||
jni_func(jobject, getPropertyDouble, jstring property);
|
||||
jni_func(void, setPropertyDouble, jstring property, jdouble value);
|
||||
jni_func(jobject, getPropertyBoolean, jstring property);
|
||||
jni_func(void, setPropertyBoolean, jstring property, jboolean value);
|
||||
jni_func(jstring, getPropertyString, jstring jproperty);
|
||||
jni_func(void, setPropertyString, jstring jproperty, jstring jvalue);
|
||||
|
||||
jni_func(void, observeProperty, jstring property, jint format);
|
||||
}
|
||||
|
||||
jni_func(jint, setOptionString, jstring joption, jstring jvalue) {
|
||||
CHECK_MPV_INIT();
|
||||
|
||||
const char *option = env->GetStringUTFChars(joption, NULL);
|
||||
const char *value = env->GetStringUTFChars(jvalue, NULL);
|
||||
|
||||
int result = mpv_set_option_string(g_mpv, option, value);
|
||||
|
||||
env->ReleaseStringUTFChars(joption, option);
|
||||
env->ReleaseStringUTFChars(jvalue, value);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
static int common_get_property(JNIEnv *env, jstring jproperty, mpv_format format, void *output)
|
||||
{
|
||||
CHECK_MPV_INIT();
|
||||
|
||||
const char *prop = env->GetStringUTFChars(jproperty, NULL);
|
||||
int result = mpv_get_property(g_mpv, prop, format, output);
|
||||
if (result == MPV_ERROR_PROPERTY_UNAVAILABLE)
|
||||
ALOGV("mpv_get_property(%s) format %d was unavailable", prop, format);
|
||||
else if (result < 0)
|
||||
ALOGE("mpv_get_property(%s) format %d returned error %s", prop, format, mpv_error_string(result));
|
||||
env->ReleaseStringUTFChars(jproperty, prop);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
static int common_set_property(JNIEnv *env, jstring jproperty, mpv_format format, void *value)
|
||||
{
|
||||
CHECK_MPV_INIT();
|
||||
|
||||
const char *prop = env->GetStringUTFChars(jproperty, NULL);
|
||||
int result = mpv_set_property(g_mpv, prop, format, value);
|
||||
if (result < 0)
|
||||
ALOGE("mpv_set_property(%s, %p) format %d returned error %s", prop, value, format, mpv_error_string(result));
|
||||
env->ReleaseStringUTFChars(jproperty, prop);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
jni_func(jobject, getPropertyInt, jstring jproperty) {
|
||||
int64_t value = 0;
|
||||
if (common_get_property(env, jproperty, MPV_FORMAT_INT64, &value) < 0)
|
||||
return NULL;
|
||||
return env->NewObject(java_Integer, java_Integer_init, (jint)value);
|
||||
}
|
||||
|
||||
jni_func(jobject, getPropertyDouble, jstring jproperty) {
|
||||
double value = 0;
|
||||
if (common_get_property(env, jproperty, MPV_FORMAT_DOUBLE, &value) < 0)
|
||||
return NULL;
|
||||
return env->NewObject(java_Double, java_Double_init, (jdouble)value);
|
||||
}
|
||||
|
||||
jni_func(jobject, getPropertyBoolean, jstring jproperty) {
|
||||
int value = 0;
|
||||
if (common_get_property(env, jproperty, MPV_FORMAT_FLAG, &value) < 0)
|
||||
return NULL;
|
||||
return env->NewObject(java_Boolean, java_Boolean_init, (jboolean)value);
|
||||
}
|
||||
|
||||
jni_func(jstring, getPropertyString, jstring jproperty) {
|
||||
char *value;
|
||||
if (common_get_property(env, jproperty, MPV_FORMAT_STRING, &value) < 0)
|
||||
return NULL;
|
||||
jstring jvalue = env->NewStringUTF(value);
|
||||
mpv_free(value);
|
||||
return jvalue;
|
||||
}
|
||||
|
||||
jni_func(void, setPropertyInt, jstring jproperty, jint jvalue) {
|
||||
int64_t value = static_cast<int64_t>(jvalue);
|
||||
common_set_property(env, jproperty, MPV_FORMAT_INT64, &value);
|
||||
}
|
||||
|
||||
jni_func(void, setPropertyDouble, jstring jproperty, jdouble jvalue) {
|
||||
double value = static_cast<double>(jvalue);
|
||||
common_set_property(env, jproperty, MPV_FORMAT_DOUBLE, &value);
|
||||
}
|
||||
|
||||
jni_func(void, setPropertyBoolean, jstring jproperty, jboolean jvalue) {
|
||||
int value = jvalue == JNI_TRUE ? 1 : 0;
|
||||
common_set_property(env, jproperty, MPV_FORMAT_FLAG, &value);
|
||||
}
|
||||
|
||||
jni_func(void, setPropertyString, jstring jproperty, jstring jvalue) {
|
||||
const char *value = env->GetStringUTFChars(jvalue, NULL);
|
||||
common_set_property(env, jproperty, MPV_FORMAT_STRING, &value);
|
||||
env->ReleaseStringUTFChars(jvalue, value);
|
||||
}
|
||||
|
||||
jni_func(void, observeProperty, jstring property, jint format) {
|
||||
CHECK_MPV_INIT();
|
||||
const char *prop = env->GetStringUTFChars(property, NULL);
|
||||
int result = mpv_observe_property(g_mpv, 0, prop, (mpv_format)format);
|
||||
if (result < 0)
|
||||
ALOGE("mpv_observe_property(%s) format %d returned error %s", prop, format, mpv_error_string(result));
|
||||
env->ReleaseStringUTFChars(property, prop);
|
||||
}
|
||||
38
app/src/main/jni/render.cpp
Normal file
38
app/src/main/jni/render.cpp
Normal file
|
|
@ -0,0 +1,38 @@
|
|||
#include <jni.h>
|
||||
|
||||
#include <mpv/client.h>
|
||||
|
||||
#include "jni_utils.h"
|
||||
#include "log.h"
|
||||
#include "globals.h"
|
||||
|
||||
extern "C" {
|
||||
jni_func(void, attachSurface, jobject surface_);
|
||||
jni_func(void, detachSurface);
|
||||
};
|
||||
|
||||
static jobject surface;
|
||||
|
||||
jni_func(void, attachSurface, jobject surface_) {
|
||||
CHECK_MPV_INIT();
|
||||
|
||||
surface = env->NewGlobalRef(surface_);
|
||||
if (!surface)
|
||||
die("invalid surface provided");
|
||||
int64_t wid = reinterpret_cast<intptr_t>(surface);
|
||||
int result = mpv_set_option(g_mpv, "wid", MPV_FORMAT_INT64, &wid);
|
||||
if (result < 0)
|
||||
ALOGE("mpv_set_option(wid) returned error %s", mpv_error_string(result));
|
||||
}
|
||||
|
||||
jni_func(void, detachSurface) {
|
||||
CHECK_MPV_INIT();
|
||||
|
||||
int64_t wid = 0;
|
||||
int result = mpv_set_option(g_mpv, "wid", MPV_FORMAT_INT64, &wid);
|
||||
if (result < 0)
|
||||
ALOGE("mpv_set_option(wid) returned error %s", mpv_error_string(result));
|
||||
|
||||
env->DeleteGlobalRef(surface);
|
||||
surface = NULL;
|
||||
}
|
||||
133
app/src/main/jni/thumbnail.cpp
Normal file
133
app/src/main/jni/thumbnail.cpp
Normal file
|
|
@ -0,0 +1,133 @@
|
|||
#include <stdlib.h>
|
||||
#include <string>
|
||||
|
||||
#include <jni.h>
|
||||
#include <android/bitmap.h>
|
||||
#include <mpv/client.h>
|
||||
|
||||
extern "C" {
|
||||
#include <libswscale/swscale.h>
|
||||
};
|
||||
|
||||
#include "jni_utils.h"
|
||||
#include "globals.h"
|
||||
#include "log.h"
|
||||
|
||||
extern "C" {
|
||||
jni_func(jobject, grabThumbnail, jint dimension);
|
||||
};
|
||||
|
||||
static inline mpv_node make_node_str(const char *s)
|
||||
{
|
||||
mpv_node r{};
|
||||
r.format = MPV_FORMAT_STRING;
|
||||
r.u.string = const_cast<char*>(s);
|
||||
return r;
|
||||
}
|
||||
|
||||
jni_func(jobject, grabThumbnail, jint dimension) {
|
||||
CHECK_MPV_INIT();
|
||||
|
||||
mpv_node result{};
|
||||
{
|
||||
mpv_node c{}, c_args[2];
|
||||
mpv_node_list c_array{};
|
||||
c_args[0] = make_node_str("screenshot-raw");
|
||||
c_args[1] = make_node_str("video");
|
||||
c_array.num = 2;
|
||||
c_array.values = c_args;
|
||||
c.format = MPV_FORMAT_NODE_ARRAY;
|
||||
c.u.list = &c_array;
|
||||
if (mpv_command_node(g_mpv, &c, &result) < 0) {
|
||||
ALOGE("screenshot-raw command failed");
|
||||
return NULL;
|
||||
}
|
||||
}
|
||||
|
||||
// extract relevant property data from the node map mpv returns
|
||||
int w = 0, h = 0, stride = 0;
|
||||
bool format_ok = false;
|
||||
struct mpv_byte_array *data = NULL;
|
||||
do {
|
||||
if (result.format != MPV_FORMAT_NODE_MAP)
|
||||
break;
|
||||
for (int i = 0; i < result.u.list->num; i++) {
|
||||
std::string key(result.u.list->keys[i]);
|
||||
const mpv_node *val = &result.u.list->values[i];
|
||||
if (key == "w" || key == "h" || key == "stride") {
|
||||
if (val->format != MPV_FORMAT_INT64)
|
||||
break;
|
||||
if (key == "w")
|
||||
w = val->u.int64;
|
||||
else if (key == "h")
|
||||
h = val->u.int64;
|
||||
else
|
||||
stride = val->u.int64;
|
||||
} else if (key == "format") {
|
||||
if (val->format != MPV_FORMAT_STRING)
|
||||
break;
|
||||
format_ok = !strcmp(val->u.string, "bgr0");
|
||||
} else if (key == "data") {
|
||||
if (val->format != MPV_FORMAT_BYTE_ARRAY)
|
||||
break;
|
||||
data = val->u.ba;
|
||||
}
|
||||
}
|
||||
} while (0);
|
||||
if (!w || !h || !stride || !format_ok || !data) {
|
||||
ALOGE("extracting data failed");
|
||||
mpv_free_node_contents(&result);
|
||||
return NULL;
|
||||
}
|
||||
ALOGV("screenshot w:%d h:%d stride:%d", w, h, stride);
|
||||
|
||||
// crop to square
|
||||
int crop_left = 0, crop_top = 0;
|
||||
int new_w = w, new_h = h;
|
||||
if (w > h) {
|
||||
crop_left = (w - h) / 2;
|
||||
new_w = h;
|
||||
} else {
|
||||
crop_top = (h - w) / 2;
|
||||
new_h = w;
|
||||
}
|
||||
ALOGV("cropped w:%u h:%u", new_w, new_h);
|
||||
|
||||
uint8_t *new_data = reinterpret_cast<uint8_t*>(data->data);
|
||||
new_data += crop_left * sizeof(uint32_t); // move begin rightwards
|
||||
new_data += stride * crop_top; // move begin downwards
|
||||
|
||||
// convert & scale to appropriate size
|
||||
struct SwsContext *ctx = sws_getContext(
|
||||
new_w, new_h, AV_PIX_FMT_BGR0,
|
||||
dimension, dimension, AV_PIX_FMT_RGB32,
|
||||
SWS_BICUBIC, NULL, NULL, NULL);
|
||||
if (!ctx) {
|
||||
mpv_free_node_contents(&result);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
jintArray arr = env->NewIntArray(dimension * dimension);
|
||||
jint *scaled = env->GetIntArrayElements(arr, NULL);
|
||||
|
||||
uint8_t *src_p[4] = { new_data }, *dst_p[4] = { (uint8_t*) scaled };
|
||||
int src_stride[4] = { stride },
|
||||
dst_stride[4] = { (int) sizeof(jint) * dimension };
|
||||
sws_scale(ctx, src_p, src_stride, 0, new_h, dst_p, dst_stride);
|
||||
sws_freeContext(ctx);
|
||||
|
||||
mpv_free_node_contents(&result); // frees data->data
|
||||
|
||||
// create android.graphics.Bitmap
|
||||
env->ReleaseIntArrayElements(arr, scaled, 0);
|
||||
|
||||
jobject bitmap_config =
|
||||
env->GetStaticObjectField(android_graphics_Bitmap_Config, android_graphics_Bitmap_Config_ARGB_8888);
|
||||
jobject bitmap =
|
||||
env->CallStaticObjectMethod(android_graphics_Bitmap, android_graphics_Bitmap_createBitmap,
|
||||
arr, dimension, dimension, bitmap_config);
|
||||
env->DeleteLocalRef(arr);
|
||||
env->DeleteLocalRef(bitmap_config);
|
||||
|
||||
return bitmap;
|
||||
}
|
||||
82
scripts/mpv/README.md
Normal file
82
scripts/mpv/README.md
Normal file
|
|
@ -0,0 +1,82 @@
|
|||
# Building
|
||||
|
||||
Compiling the native parts is a process separate from Gradle and the app won't work if you skip this.
|
||||
|
||||
This process is supported on Linux and macOS. Windows (or WSL) will **not** work.
|
||||
|
||||
## Download dependencies
|
||||
|
||||
`download.sh` will take care of installing the Android SDK, NDK and downloading the sources.
|
||||
|
||||
If you're running on Debian/Ubuntu or RHEL/Fedora it will also install the necessary dependencies for you.
|
||||
|
||||
```sh
|
||||
./download.sh
|
||||
```
|
||||
|
||||
If you already have the Android SDK installed you can symlink `android-sdk-linux` to your SDK root
|
||||
before running the script and the necessary SDK packages will still be installed.
|
||||
|
||||
A matching NDK version (inside the SDK) will be picked up automatically or downloaded and installed otherwise.
|
||||
|
||||
## Build
|
||||
|
||||
```sh
|
||||
./buildall.sh
|
||||
```
|
||||
|
||||
Run `buildall.sh` with `--clean` to clean the build directories before building.
|
||||
For a guaranteed clean build also run `rm -rf prefix` beforehand.
|
||||
|
||||
By default this will build only for 32-bit ARM (`armv7l`).
|
||||
You probably want to build for AArch64 too, and perhaps Intel x86.
|
||||
|
||||
To do this run one (or more) of these commands **before** ./buildall.sh:
|
||||
```sh
|
||||
./buildall.sh --arch arm64 mpv
|
||||
./buildall.sh --arch x86 mpv
|
||||
./buildall.sh --arch x86_64 mpv
|
||||
```
|
||||
|
||||
# Developing
|
||||
|
||||
## Getting logs
|
||||
|
||||
```sh
|
||||
adb logcat # get all logs, useful when drivers/vendor libs output to logcat
|
||||
adb logcat -s mpv # get only mpv logs
|
||||
```
|
||||
|
||||
## Rebuilding a single component
|
||||
|
||||
If you've made changes to a single component (e.g. ffmpeg or mpv) and want a new build you can of course just run ./buildall.sh but it's also possible to just build a single component like this:
|
||||
|
||||
```sh
|
||||
./buildall.sh -n ffmpeg
|
||||
# optional: add --clean to build from a clean state
|
||||
```
|
||||
|
||||
Note that you might need to rebuild for other architectures (`--arch`) too depending on your device.
|
||||
|
||||
Afterwards, build mpv-android and install the apk:
|
||||
|
||||
```sh
|
||||
./buildall.sh -n
|
||||
adb install -r ../app/build/outputs/apk/default/debug/app-default-universal-debug.apk
|
||||
```
|
||||
|
||||
## Using Android Studio
|
||||
|
||||
You can use Android Studio to develop the Java part of the codebase. Before using it, make sure to build the project at least once by following the steps in the **Build** section.
|
||||
|
||||
You should point Android Studio to existing SDK installation at `mpv-android/buildscripts/sdk/android-sdk-linux`.
|
||||
Then click "Open an existing Android Studio project" and select `mpv-android`.
|
||||
|
||||
Note that if you build from Android Studio only the Java/Kotlin part will be built.
|
||||
If you make any changes to libraries (ffmpeg, mpv, ...) or mpv-android native code (`app/src/main/jni/*`), first rebuild native code with:
|
||||
|
||||
```sh
|
||||
./buildall.sh -n
|
||||
```
|
||||
|
||||
then build the project from Android Studio.
|
||||
180
scripts/mpv/buildall.sh
Executable file
180
scripts/mpv/buildall.sh
Executable file
|
|
@ -0,0 +1,180 @@
|
|||
#!/bin/bash -e
|
||||
|
||||
cd "$( dirname "${BASH_SOURCE[0]}" )"
|
||||
. ./include/depinfo.sh
|
||||
|
||||
cleanbuild=0
|
||||
nodeps=0
|
||||
clang=1
|
||||
target=mpv-android
|
||||
arch=armv7l
|
||||
|
||||
getdeps () {
|
||||
varname="dep_${1//-/_}[*]"
|
||||
echo ${!varname}
|
||||
}
|
||||
|
||||
loadarch () {
|
||||
unset CC CXX CPATH LIBRARY_PATH C_INCLUDE_PATH CPLUS_INCLUDE_PATH
|
||||
unset CFLAGS CXXFLAGS CPPFLAGS LDFLAGS
|
||||
|
||||
local apilvl=21
|
||||
# ndk_triple: what the toolchain actually is
|
||||
# cc_triple: what Google pretends the toolchain is
|
||||
if [ "$1" == "armv7l" ]; then
|
||||
export ndk_suffix=
|
||||
export ndk_triple=arm-linux-androideabi
|
||||
cc_triple=armv7a-linux-androideabi$apilvl
|
||||
prefix_name=armv7l
|
||||
elif [ "$1" == "arm64" ]; then
|
||||
export ndk_suffix=-arm64
|
||||
export ndk_triple=aarch64-linux-android
|
||||
cc_triple=$ndk_triple$apilvl
|
||||
prefix_name=arm64
|
||||
elif [ "$1" == "x86" ]; then
|
||||
export ndk_suffix=-x86
|
||||
export ndk_triple=i686-linux-android
|
||||
cc_triple=$ndk_triple$apilvl
|
||||
prefix_name=x86
|
||||
elif [ "$1" == "x86_64" ]; then
|
||||
export ndk_suffix=-x64
|
||||
export ndk_triple=x86_64-linux-android
|
||||
cc_triple=$ndk_triple$apilvl
|
||||
prefix_name=x86_64
|
||||
else
|
||||
echo "Invalid architecture" >&2
|
||||
exit 1
|
||||
fi
|
||||
export prefix_dir="$PWD/prefix/$prefix_name"
|
||||
if [ $clang -eq 1 ]; then
|
||||
export CC=$cc_triple-clang
|
||||
export CXX=$cc_triple-clang++
|
||||
else
|
||||
export CC=$cc_triple-gcc
|
||||
export CXX=$cc_triple-g++
|
||||
fi
|
||||
export LDFLAGS="-Wl,-O1,--icf=safe -Wl,-z,max-page-size=16384"
|
||||
export AR=llvm-ar
|
||||
export RANLIB=llvm-ranlib
|
||||
}
|
||||
|
||||
setup_prefix () {
|
||||
if [ ! -d "$prefix_dir" ]; then
|
||||
mkdir -p "$prefix_dir"
|
||||
# enforce flat structure (/usr/local -> /)
|
||||
ln -s . "$prefix_dir/usr"
|
||||
ln -s . "$prefix_dir/local"
|
||||
fi
|
||||
|
||||
local cpu_family=${ndk_triple%%-*}
|
||||
[ "$cpu_family" == "i686" ] && cpu_family=x86
|
||||
|
||||
if ! command -v pkg-config >/dev/null; then
|
||||
echo "pkg-config not provided!"
|
||||
return 1
|
||||
fi
|
||||
|
||||
# meson wants to be spoonfed this file, so create it ahead of time
|
||||
# also define: release build, static libs and no source downloads at runtime(!!!)
|
||||
cat >"$prefix_dir/crossfile.tmp" <<CROSSFILE
|
||||
[built-in options]
|
||||
buildtype = 'release'
|
||||
default_library = 'static'
|
||||
wrap_mode = 'nodownload'
|
||||
prefix = '/usr/local'
|
||||
[binaries]
|
||||
c = '$CC'
|
||||
cpp = '$CXX'
|
||||
ar = 'llvm-ar'
|
||||
nm = 'llvm-nm'
|
||||
strip = 'llvm-strip'
|
||||
pkgconfig = 'pkg-config'
|
||||
pkg-config = 'pkg-config'
|
||||
[host_machine]
|
||||
system = 'android'
|
||||
cpu_family = '$cpu_family'
|
||||
cpu = '${CC%%-*}'
|
||||
endian = 'little'
|
||||
CROSSFILE
|
||||
# also avoid rewriting it needlessly
|
||||
if cmp -s "$prefix_dir"/crossfile.{tmp,txt}; then
|
||||
rm "$prefix_dir/crossfile.tmp"
|
||||
else
|
||||
mv "$prefix_dir"/crossfile.{tmp,txt}
|
||||
fi
|
||||
}
|
||||
|
||||
build () {
|
||||
if [ $1 != "mpv-android" ] && [ ! -d deps/$1 ]; then
|
||||
printf >&2 '\e[1;31m%s\e[m\n' "Target $1 not found"
|
||||
return 1
|
||||
fi
|
||||
if [ $nodeps -eq 0 ]; then
|
||||
printf >&2 '\e[1;34m%s\e[m\n' "Preparing $1..."
|
||||
local deps=$(getdeps $1)
|
||||
echo >&2 "Dependencies: $deps"
|
||||
for dep in $deps; do
|
||||
build $dep
|
||||
done
|
||||
fi
|
||||
printf >&2 '\e[1;34m%s\e[m\n' "Building $1..."
|
||||
if [ "$1" == "mpv-android" ]; then
|
||||
pushd ..
|
||||
BUILDSCRIPT=buildscripts/scripts/$1.sh
|
||||
else
|
||||
pushd deps/$1
|
||||
BUILDSCRIPT=../../scripts/$1.sh
|
||||
fi
|
||||
[ $cleanbuild -eq 1 ] && $BUILDSCRIPT clean
|
||||
$BUILDSCRIPT build
|
||||
popd
|
||||
}
|
||||
|
||||
usage () {
|
||||
printf '%s\n' \
|
||||
"Usage: buildall.sh [options] [target]" \
|
||||
"Builds the specified target (default: $target)" \
|
||||
"-n Do not build dependencies" \
|
||||
"--clean Clean build dirs before compiling" \
|
||||
"--gcc Use gcc compiler (unsupported!)" \
|
||||
"--arch <arch> Build for specified architecture (default: $arch; supported: armv7l, arm64, x86, x86_64)"
|
||||
exit 0
|
||||
}
|
||||
|
||||
while [ $# -gt 0 ]; do
|
||||
case "$1" in
|
||||
--clean)
|
||||
cleanbuild=1
|
||||
;;
|
||||
-n|--no-deps)
|
||||
nodeps=1
|
||||
;;
|
||||
--gcc)
|
||||
clang=0
|
||||
;;
|
||||
--arch)
|
||||
shift
|
||||
arch=$1
|
||||
;;
|
||||
-h|--help)
|
||||
usage
|
||||
;;
|
||||
-*)
|
||||
echo "Unknown flag $1" >&2
|
||||
exit 1
|
||||
;;
|
||||
*)
|
||||
target=$1
|
||||
;;
|
||||
esac
|
||||
shift
|
||||
done
|
||||
|
||||
loadarch $arch
|
||||
setup_prefix
|
||||
build $target
|
||||
|
||||
[ "$target" == "mpv-android" ] && \
|
||||
ls -lh ../app/build/outputs/apk/{default,api29}/*/*.apk
|
||||
|
||||
exit 0
|
||||
68
scripts/mpv/get_dependencies.sh
Executable file
68
scripts/mpv/get_dependencies.sh
Executable file
|
|
@ -0,0 +1,68 @@
|
|||
#!/usr/bin/env bash
|
||||
|
||||
set -exou pipefail
|
||||
|
||||
BUILD_PATH="./deps"
|
||||
|
||||
mkdir -p "$BUILD_PATH"
|
||||
pushd "$BUILD_PATH" || exit
|
||||
|
||||
function clone(){
|
||||
repo=$1
|
||||
branch=$2
|
||||
dir=$3
|
||||
shift 3
|
||||
|
||||
if [[ -d "$dir" ]]; then
|
||||
pushd "$dir" || exit
|
||||
git checkout --force "$branch"
|
||||
popd || exit
|
||||
else
|
||||
git clone "$repo" --depth 1 --single-branch -b "$branch" "$dir" "$@"
|
||||
fi
|
||||
}
|
||||
|
||||
clone "https://github.com/videolan/dav1d" "1.5.2" dav1d
|
||||
|
||||
clone "https://github.com/FFmpeg/FFmpeg" "n8.0" ffmpeg
|
||||
|
||||
clone "https://gitlab.freedesktop.org/freetype/freetype.git" "VER-2-14-1" freetype2 --recurse-submodules
|
||||
|
||||
clone "https://github.com/libass/libass" "master" libass
|
||||
|
||||
clone "https://github.com/haasn/libplacebo" "master" libplacebo --recurse-submodules
|
||||
|
||||
clone "https://github.com/mpv-player/mpv" "master" mpv
|
||||
|
||||
if [[ ! -d mbedtls ]]; then
|
||||
mkdir mbedtls
|
||||
wget https://github.com/Mbed-TLS/mbedtls/releases/download/mbedtls-3.6.4/mbedtls-3.6.4.tar.bz2 -O - | \
|
||||
tar -xj -C mbedtls --strip-components=1
|
||||
fi
|
||||
|
||||
if [[ ! -d fribidi ]]; then
|
||||
mkdir fribidi
|
||||
wget https://github.com/fribidi/fribidi/releases/download/v1.0.16/fribidi-1.0.16.tar.xz -O - | \
|
||||
tar -xJ -C fribidi --strip-components=1
|
||||
fi
|
||||
|
||||
if [[ ! -d harfbuzz ]]; then
|
||||
mkdir harfbuzz
|
||||
wget https://github.com/harfbuzz/harfbuzz/releases/download/12.1.0/harfbuzz-12.1.0.tar.xz -O - | \
|
||||
tar -xJ -C harfbuzz --strip-components=1
|
||||
fi
|
||||
|
||||
version_unibreak="6.1"
|
||||
if [[ ! -d unibreak ]]; then
|
||||
mkdir unibreak
|
||||
wget https://github.com/adah1972/libunibreak/releases/download/libunibreak_${version_unibreak//./_}/libunibreak-${version_unibreak}.tar.gz -O - | \
|
||||
tar -xz -C unibreak --strip-components=1
|
||||
fi
|
||||
|
||||
if [[ ! -d lua ]]; then
|
||||
mkdir lua
|
||||
wget https://www.lua.org/ftp/lua-5.2.4.tar.gz -O - | \
|
||||
tar -xz -C lua --strip-components=1
|
||||
fi
|
||||
|
||||
# python packages: jsonschema jinja2 meson
|
||||
91
scripts/mpv/include/ci.sh
Executable file
91
scripts/mpv/include/ci.sh
Executable file
|
|
@ -0,0 +1,91 @@
|
|||
#!/bin/bash -e
|
||||
|
||||
# go to buildscripts root folder
|
||||
cd "$( dirname "${BASH_SOURCE[0]}" )/.."
|
||||
|
||||
. ./include/depinfo.sh
|
||||
|
||||
msg() {
|
||||
printf '==> %s\n' "$1"
|
||||
}
|
||||
|
||||
fetch_prefix() {
|
||||
if [[ "$CACHE_MODE" == folder ]]; then
|
||||
local text=
|
||||
if [ -f "$CACHE_FOLDER/id.txt" ]; then
|
||||
text=$(cat "$CACHE_FOLDER/id.txt")
|
||||
else
|
||||
echo "Cache seems to be empty"
|
||||
fi
|
||||
printf 'Expecting "%s",\nfound "%s".\n' "$ci_tarball" "$text"
|
||||
if [[ "$text" == "$ci_tarball" ]]; then
|
||||
tar -xzf "$CACHE_FOLDER/data.tgz" -C prefix && return 0
|
||||
fi
|
||||
fi
|
||||
return 1
|
||||
}
|
||||
|
||||
build_prefix() {
|
||||
msg "Building the prefix ($ci_tarball)..."
|
||||
|
||||
msg "Fetching deps"
|
||||
IN_CI=1 ./include/download-deps.sh
|
||||
|
||||
# build everything mpv depends on (but not mpv itself)
|
||||
for x in ${dep_mpv[@]}; do
|
||||
msg "Building $x"
|
||||
./buildall.sh $x
|
||||
done
|
||||
|
||||
if [[ "$CACHE_MODE" == folder && -w "$CACHE_FOLDER" ]]; then
|
||||
msg "Compressing the prefix"
|
||||
tar -cvzf "$CACHE_FOLDER/data.tgz" -C prefix .
|
||||
echo "$ci_tarball" >"$CACHE_FOLDER/id.txt"
|
||||
fi
|
||||
}
|
||||
|
||||
export WGET="wget --progress=bar:force"
|
||||
|
||||
if [ "$1" = "export" ]; then
|
||||
# export variable with unique cache identifier
|
||||
echo "CACHE_IDENTIFIER=$ci_tarball"
|
||||
exit 0
|
||||
elif [ "$1" = "install" ]; then
|
||||
# install deps
|
||||
if [[ -n "$ANDROID_HOME" && -d "$ANDROID_HOME" ]]; then
|
||||
msg "Linking existing SDK"
|
||||
mkdir -p sdk
|
||||
ln -sv "$ANDROID_HOME" sdk/android-sdk-linux
|
||||
fi
|
||||
|
||||
msg "Fetching SDK + NDK"
|
||||
IN_CI=1 ./include/download-sdk.sh
|
||||
|
||||
msg "Fetching mpv"
|
||||
mkdir -p deps/mpv
|
||||
$WGET https://github.com/mpv-player/mpv/archive/master.tar.gz -O master.tgz
|
||||
tar -xzf master.tgz -C deps/mpv --strip-components=1
|
||||
rm master.tgz
|
||||
|
||||
msg "Trying to fetch existing prefix"
|
||||
mkdir -p prefix
|
||||
fetch_prefix || build_prefix
|
||||
exit 0
|
||||
elif [ "$1" = "build" ]; then
|
||||
# run build
|
||||
:
|
||||
else
|
||||
exit 1
|
||||
fi
|
||||
|
||||
msg "Building mpv"
|
||||
./buildall.sh -n mpv || {
|
||||
# show logfile if configure failed
|
||||
[ ! -f deps/mpv/_build/config.h ] && cat deps/mpv/_build/meson-logs/meson-log.txt
|
||||
exit 1
|
||||
}
|
||||
|
||||
msg "Building mpv-android"
|
||||
./buildall.sh -n
|
||||
|
||||
exit 0
|
||||
43
scripts/mpv/include/depinfo.sh
Executable file
43
scripts/mpv/include/depinfo.sh
Executable file
|
|
@ -0,0 +1,43 @@
|
|||
#!/bin/bash -e
|
||||
|
||||
## Dependency versions
|
||||
# Make sure to keep v_ndk and v_ndk_n in sync, both are listed on the NDK download page
|
||||
|
||||
v_sdk=11076708_latest
|
||||
v_ndk=r29
|
||||
v_ndk_n=29.0.14206865
|
||||
v_sdk_platform=35
|
||||
v_sdk_build_tools=35.0.0
|
||||
|
||||
v_lua=5.2.4
|
||||
v_unibreak=6.1
|
||||
v_harfbuzz=12.1.0
|
||||
v_fribidi=1.0.16
|
||||
v_freetype=2.14.1
|
||||
v_mbedtls=3.6.4
|
||||
|
||||
|
||||
## Dependency tree
|
||||
# I would've used a dict but putting arrays in a dict is not a thing
|
||||
|
||||
dep_mbedtls=()
|
||||
dep_dav1d=()
|
||||
dep_ffmpeg=(mbedtls dav1d)
|
||||
dep_freetype2=()
|
||||
dep_fribidi=()
|
||||
dep_harfbuzz=()
|
||||
dep_unibreak=()
|
||||
dep_libass=(freetype2 fribidi harfbuzz unibreak)
|
||||
dep_lua=()
|
||||
dep_libplacebo=()
|
||||
dep_mpv=(ffmpeg libass lua libplacebo)
|
||||
dep_mpv_android=(mpv)
|
||||
|
||||
|
||||
## for CI workflow
|
||||
|
||||
# pinned ffmpeg revision
|
||||
v_ci_ffmpeg=n8.0
|
||||
|
||||
# filename used to uniquely identify a build prefix
|
||||
ci_tarball="prefix-ndk-${v_ndk}-lua-${v_lua}-unibreak-${v_unibreak}-harfbuzz-${v_harfbuzz}-fribidi-${v_fribidi}-freetype-${v_freetype}-mbedtls-${v_mbedtls}-ffmpeg-${v_ci_ffmpeg}.tgz"
|
||||
32
scripts/mpv/include/path.sh
Executable file
32
scripts/mpv/include/path.sh
Executable file
|
|
@ -0,0 +1,32 @@
|
|||
#!/bin/bash
|
||||
|
||||
DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && cd .. && pwd )"
|
||||
|
||||
. "$DIR/include/depinfo.sh"
|
||||
|
||||
os=linux
|
||||
[[ "$OSTYPE" == "darwin"* ]] && os=mac
|
||||
export os
|
||||
|
||||
if [ "$os" == "mac" ]; then
|
||||
[ -z "$cores" ] && cores=$(sysctl -n hw.ncpu)
|
||||
# various things rely on GNU behaviour
|
||||
export INSTALL=`which ginstall`
|
||||
export SED=gsed
|
||||
else
|
||||
[ -z "$cores" ] && cores=$(grep -c ^processor /proc/cpuinfo)
|
||||
fi
|
||||
cores=${cores:-4}
|
||||
|
||||
# configure pkg-config paths if inside buildscripts
|
||||
if [ -n "$ndk_triple" ]; then
|
||||
export PKG_CONFIG_SYSROOT_DIR="$prefix_dir"
|
||||
export PKG_CONFIG_LIBDIR="$PKG_CONFIG_SYSROOT_DIR/lib/pkgconfig"
|
||||
unset PKG_CONFIG_PATH
|
||||
fi
|
||||
|
||||
toolchain=$(echo "$DIR/sdk/android-ndk-${v_ndk}/toolchains/llvm/prebuilt/"*)
|
||||
[ -d "$toolchain" ] && \
|
||||
export PATH="$toolchain/bin:$DIR/sdk/android-ndk-${v_ndk}:$DIR/sdk/bin:$PATH"
|
||||
export ANDROID_HOME="$DIR/sdk/android-sdk-$os"
|
||||
unset ANDROID_SDK_ROOT ANDROID_NDK_ROOT
|
||||
22
scripts/mpv/scripts/dav1d.sh
Executable file
22
scripts/mpv/scripts/dav1d.sh
Executable file
|
|
@ -0,0 +1,22 @@
|
|||
#!/bin/bash -e
|
||||
|
||||
. ../../include/path.sh
|
||||
|
||||
build=_build$ndk_suffix
|
||||
|
||||
if [ "$1" == "build" ]; then
|
||||
true
|
||||
elif [ "$1" == "clean" ]; then
|
||||
rm -rf $build
|
||||
exit 0
|
||||
else
|
||||
exit 255
|
||||
fi
|
||||
|
||||
unset CC CXX # meson wants these unset
|
||||
|
||||
meson setup $build --cross-file "$prefix_dir"/crossfile.txt \
|
||||
-Denable_tests=false -Db_lto=true -Dstack_alignment=16
|
||||
|
||||
ninja -C $build -j$cores
|
||||
DESTDIR="$prefix_dir" ninja -C $build install
|
||||
48
scripts/mpv/scripts/ffmpeg.sh
Executable file
48
scripts/mpv/scripts/ffmpeg.sh
Executable file
|
|
@ -0,0 +1,48 @@
|
|||
#!/bin/bash -e
|
||||
|
||||
. ../../include/path.sh
|
||||
|
||||
if [ "$1" == "build" ]; then
|
||||
true
|
||||
elif [ "$1" == "clean" ]; then
|
||||
rm -rf _build$ndk_suffix
|
||||
exit 0
|
||||
else
|
||||
exit 255
|
||||
fi
|
||||
|
||||
mkdir -p _build$ndk_suffix
|
||||
cd _build$ndk_suffix
|
||||
|
||||
cpu=armv7-a
|
||||
[[ "$ndk_triple" == "aarch64"* ]] && cpu=armv8-a
|
||||
[[ "$ndk_triple" == "x86_64"* ]] && cpu=generic
|
||||
[[ "$ndk_triple" == "i686"* ]] && cpu="i686 --disable-asm"
|
||||
|
||||
cpuflags=
|
||||
[[ "$ndk_triple" == "arm"* ]] && cpuflags="$cpuflags -mfpu=neon -mcpu=cortex-a8"
|
||||
|
||||
args=(
|
||||
--target-os=android --enable-cross-compile
|
||||
--cross-prefix=$ndk_triple- --cc=$CC --pkg-config=pkg-config --nm=llvm-nm
|
||||
--arch=${ndk_triple%%-*} --cpu=$cpu
|
||||
--extra-cflags="-I$prefix_dir/include $cpuflags" --extra-ldflags="-L$prefix_dir/lib"
|
||||
|
||||
--enable-{jni,mediacodec,mbedtls,libdav1d} --disable-vulkan
|
||||
--disable-static --enable-shared --enable-{gpl,version3}
|
||||
|
||||
# disable unneeded parts
|
||||
--disable-{stripping,doc,programs}
|
||||
# to keep the build lean we disable some feature quite aggressively:
|
||||
# - muxers, encoders: mpv-android does not have any way to use these
|
||||
# - devices: no practical use on Android
|
||||
--disable-{muxers,encoders,devices}
|
||||
# useful to taking screenshots
|
||||
--enable-encoder=mjpeg,png
|
||||
# useful for the `dump-cache` command
|
||||
--enable-muxer=mov,matroska,mpegts
|
||||
)
|
||||
../configure "${args[@]}"
|
||||
|
||||
make -j$cores
|
||||
make DESTDIR="$prefix_dir" install
|
||||
21
scripts/mpv/scripts/freetype2.sh
Executable file
21
scripts/mpv/scripts/freetype2.sh
Executable file
|
|
@ -0,0 +1,21 @@
|
|||
#!/bin/bash -e
|
||||
|
||||
. ../../include/path.sh
|
||||
|
||||
build=_build$ndk_suffix
|
||||
|
||||
if [ "$1" == "build" ]; then
|
||||
true
|
||||
elif [ "$1" == "clean" ]; then
|
||||
rm -rf $build
|
||||
exit 0
|
||||
else
|
||||
exit 255
|
||||
fi
|
||||
|
||||
unset CC CXX # meson wants these unset
|
||||
|
||||
meson setup $build --cross-file "$prefix_dir"/crossfile.txt
|
||||
|
||||
ninja -C $build -j$cores
|
||||
DESTDIR="$prefix_dir" ninja -C $build install
|
||||
22
scripts/mpv/scripts/fribidi.sh
Executable file
22
scripts/mpv/scripts/fribidi.sh
Executable file
|
|
@ -0,0 +1,22 @@
|
|||
#!/bin/bash -e
|
||||
|
||||
. ../../include/path.sh
|
||||
|
||||
build=_build$ndk_suffix
|
||||
|
||||
if [ "$1" == "build" ]; then
|
||||
true
|
||||
elif [ "$1" == "clean" ]; then
|
||||
rm -rf $build
|
||||
exit 0
|
||||
else
|
||||
exit 255
|
||||
fi
|
||||
|
||||
unset CC CXX # meson wants these unset
|
||||
|
||||
meson setup $build --cross-file "$prefix_dir"/crossfile.txt \
|
||||
-D{tests,docs}=false
|
||||
|
||||
ninja -C $build -j$cores
|
||||
DESTDIR="$prefix_dir" ninja -C $build install
|
||||
22
scripts/mpv/scripts/harfbuzz.sh
Executable file
22
scripts/mpv/scripts/harfbuzz.sh
Executable file
|
|
@ -0,0 +1,22 @@
|
|||
#!/bin/bash -e
|
||||
|
||||
. ../../include/path.sh
|
||||
|
||||
build=_build$ndk_suffix
|
||||
|
||||
if [ "$1" == "build" ]; then
|
||||
true
|
||||
elif [ "$1" == "clean" ]; then
|
||||
rm -rf $build
|
||||
exit 0
|
||||
else
|
||||
exit 255
|
||||
fi
|
||||
|
||||
unset CC CXX # meson wants these unset
|
||||
|
||||
meson setup $build --cross-file "$prefix_dir"/crossfile.txt \
|
||||
-Dtests=disabled -Ddocs=disabled
|
||||
|
||||
ninja -C $build -j$cores
|
||||
DESTDIR="$prefix_dir" ninja -C $build install
|
||||
25
scripts/mpv/scripts/libass.sh
Executable file
25
scripts/mpv/scripts/libass.sh
Executable file
|
|
@ -0,0 +1,25 @@
|
|||
#!/bin/bash -e
|
||||
|
||||
. ../../include/path.sh
|
||||
|
||||
if [ "$1" == "build" ]; then
|
||||
true
|
||||
elif [ "$1" == "clean" ]; then
|
||||
rm -rf _build$ndk_suffix
|
||||
exit 0
|
||||
else
|
||||
exit 255
|
||||
fi
|
||||
|
||||
[ -f configure ] || ./autogen.sh
|
||||
|
||||
mkdir -p _build$ndk_suffix
|
||||
cd _build$ndk_suffix
|
||||
|
||||
../configure \
|
||||
--host=$ndk_triple --with-pic \
|
||||
--enable-static --disable-shared \
|
||||
--enable-libunibreak --disable-require-system-font-provider
|
||||
|
||||
make -j$cores
|
||||
make DESTDIR="$prefix_dir" install
|
||||
25
scripts/mpv/scripts/libplacebo.sh
Executable file
25
scripts/mpv/scripts/libplacebo.sh
Executable file
|
|
@ -0,0 +1,25 @@
|
|||
#!/bin/bash -e
|
||||
|
||||
. ../../include/path.sh
|
||||
|
||||
build=_build$ndk_suffix
|
||||
|
||||
if [ "$1" == "build" ]; then
|
||||
true
|
||||
elif [ "$1" == "clean" ]; then
|
||||
rm -rf $build
|
||||
exit 0
|
||||
else
|
||||
exit 255
|
||||
fi
|
||||
|
||||
unset CC CXX
|
||||
meson setup $build --cross-file "$prefix_dir"/crossfile.txt \
|
||||
-Dvulkan=disabled -Ddemos=false
|
||||
|
||||
ninja -C $build -j$cores
|
||||
DESTDIR="$prefix_dir" ninja -C $build install
|
||||
|
||||
# add missing library for static linking
|
||||
# this isn't "-lstdc++" due to a meson bug: https://github.com/mesonbuild/meson/issues/11300
|
||||
${SED:-sed} '/^Libs:/ s|$| -lc++|' "$prefix_dir/lib/pkgconfig/libplacebo.pc" -i
|
||||
44
scripts/mpv/scripts/lua.sh
Executable file
44
scripts/mpv/scripts/lua.sh
Executable file
|
|
@ -0,0 +1,44 @@
|
|||
#!/bin/bash -e
|
||||
|
||||
. ../../include/path.sh
|
||||
|
||||
if [ "$1" == "build" ]; then
|
||||
true
|
||||
elif [ "$1" == "clean" ]; then
|
||||
make clean
|
||||
exit 0
|
||||
else
|
||||
exit 255
|
||||
fi
|
||||
|
||||
# Building seperately from source tree is not supported, this means we are forced to always clean
|
||||
$0 clean
|
||||
|
||||
mycflags=(
|
||||
# ensures correct linking into libmpv.so
|
||||
-fPIC
|
||||
# bionic is missing decimal_point in localeconv [src/llex.c]
|
||||
-Dgetlocaledecpoint\\\(\\\)=\\\(46\\\)
|
||||
# force fallback as ftello/fseeko are not defined [src/liolib.c]
|
||||
-Dlua_fseek
|
||||
)
|
||||
|
||||
# LUA_T= and LUAC_T= to disable building lua & luac
|
||||
# -Dgetlocaledecpoint()=('.') fixes bionic missing decimal_point in localeconv
|
||||
make CC="$CC" AR="$AR rc" RANLIB="$RANLIB" \
|
||||
MYCFLAGS="${mycflags[*]}" \
|
||||
PLAT=linux LUA_T= LUAC_T= -j$cores
|
||||
|
||||
# TO_BIN=/dev/null disables installing lua & luac
|
||||
make INSTALL=${INSTALL:-install} INSTALL_TOP="$prefix_dir" TO_BIN=/dev/null install
|
||||
|
||||
# make pc only generates a partial pkg-config file because ????
|
||||
mkdir -p $prefix_dir/lib/pkgconfig
|
||||
make pc >$prefix_dir/lib/pkgconfig/lua.pc
|
||||
cat >>$prefix_dir/lib/pkgconfig/lua.pc <<'EOF'
|
||||
Name: Lua
|
||||
Description:
|
||||
Version: ${version}
|
||||
Libs: -L${libdir} -llua
|
||||
Cflags: -I${includedir}
|
||||
EOF
|
||||
22
scripts/mpv/scripts/mbedtls.sh
Executable file
22
scripts/mpv/scripts/mbedtls.sh
Executable file
|
|
@ -0,0 +1,22 @@
|
|||
#!/bin/bash -e
|
||||
|
||||
. ../../include/path.sh
|
||||
|
||||
if [ "$1" == "build" ]; then
|
||||
true
|
||||
elif [ "$1" == "clean" ]; then
|
||||
make clean
|
||||
exit 0
|
||||
else
|
||||
exit 255
|
||||
fi
|
||||
|
||||
$0 clean # separate building not supported, always clean
|
||||
if [[ "$ndk_triple" == "i686"* ]]; then
|
||||
./scripts/config.py unset MBEDTLS_AESNI_C
|
||||
else
|
||||
./scripts/config.py set MBEDTLS_AESNI_C
|
||||
fi
|
||||
|
||||
make -j$cores no_test
|
||||
make DESTDIR="$prefix_dir" install
|
||||
77
scripts/mpv/scripts/mpv-android.sh
Executable file
77
scripts/mpv/scripts/mpv-android.sh
Executable file
|
|
@ -0,0 +1,77 @@
|
|||
#!/bin/bash -e
|
||||
|
||||
DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )"
|
||||
BUILD="$DIR/.."
|
||||
MPV_ANDROID="$DIR/../.."
|
||||
|
||||
. $BUILD/include/path.sh
|
||||
. $BUILD/include/depinfo.sh
|
||||
|
||||
if [ "$1" == "build" ]; then
|
||||
true
|
||||
elif [ "$1" == "clean" ]; then
|
||||
rm -rf $MPV_ANDROID/{app,.}/build $MPV_ANDROID/app/src/main/{libs,obj}
|
||||
exit 0
|
||||
else
|
||||
exit 255
|
||||
fi
|
||||
|
||||
[ -n "$ANDROID_SIGNING_KEY" ] && BUNDLE=1
|
||||
|
||||
nativeprefix () {
|
||||
if [ -f $BUILD/prefix/$1/lib/libmpv.so ]; then
|
||||
echo $BUILD/prefix/$1
|
||||
else
|
||||
echo >&2 "Warning: libmpv.so not found in native prefix for $1, support will be omitted"
|
||||
fi
|
||||
}
|
||||
|
||||
prefix32=$(nativeprefix "armv7l")
|
||||
prefix64=$(nativeprefix "arm64")
|
||||
prefix_x64=$(nativeprefix "x86_64")
|
||||
prefix_x86=$(nativeprefix "x86")
|
||||
|
||||
if [[ -z "$prefix32" && -z "$prefix64" && -z "$prefix_x64" && -z "$prefix_x86" ]]; then
|
||||
echo >&2 "Error: no mpv library detected."
|
||||
exit 255
|
||||
fi
|
||||
|
||||
PREFIX32=$prefix32 PREFIX64=$prefix64 PREFIX_X64=$prefix_x64 PREFIX_X86=$prefix_x86 \
|
||||
ndk-build -C app/src/main -j$cores
|
||||
|
||||
targets=(assembleDebug)
|
||||
if [ -z "$DONT_BUILD_RELEASE" ]; then
|
||||
targets+=(assembleRelease)
|
||||
[ -n "$BUNDLE" ] && targets+=(bundleRelease)
|
||||
fi
|
||||
./gradlew "${targets[@]}"
|
||||
|
||||
if [ -n "$ANDROID_SIGNING_KEY" ]; then
|
||||
cd "${MPV_ANDROID}/app/build/outputs/apk"
|
||||
apksigner=${ANDROID_HOME}/build-tools/${v_sdk_build_tools}/apksigner
|
||||
for v in default api29; do
|
||||
pushd $v
|
||||
# sign the universal debug APK
|
||||
"$apksigner" sign --ks "${ANDROID_SIGNING_KEY}" \
|
||||
--in debug/app-$v-universal-debug.apk --out debug/app-$v-universal-debug-signed.apk
|
||||
# but all of the release APKs
|
||||
for apk in release/*-unsigned.apk; do
|
||||
"$apksigner" sign --ks "${ANDROID_SIGNING_KEY}" \
|
||||
--in $apk --out ${apk/-unsigned/-signed}
|
||||
done
|
||||
popd
|
||||
done
|
||||
# and the bundle
|
||||
cd ../bundle
|
||||
if [ -n "$BUNDLE" ]; then
|
||||
if [ -z "$ANDROID_SIGNING_ALIAS" ]; then
|
||||
echo >&2 "Error: ANDROID_SIGNING_ALIAS must be set to use jarsigner"
|
||||
exit 1
|
||||
fi
|
||||
pushd defaultRelease
|
||||
jarsigner -keystore "${ANDROID_SIGNING_KEY}" -signedjar \
|
||||
app-default-release-signed.aab app-default-release.aab \
|
||||
"${ANDROID_SIGNING_ALIAS}"
|
||||
popd
|
||||
fi
|
||||
fi
|
||||
30
scripts/mpv/scripts/mpv.sh
Executable file
30
scripts/mpv/scripts/mpv.sh
Executable file
|
|
@ -0,0 +1,30 @@
|
|||
#!/bin/bash -e
|
||||
|
||||
. ../../include/path.sh
|
||||
|
||||
build=_build$ndk_suffix
|
||||
|
||||
if [ "$1" == "build" ]; then
|
||||
true
|
||||
elif [ "$1" == "clean" ]; then
|
||||
rm -rf $build
|
||||
exit 0
|
||||
else
|
||||
exit 255
|
||||
fi
|
||||
|
||||
unset CC CXX # meson wants these unset
|
||||
|
||||
meson setup $build --cross-file "$prefix_dir"/crossfile.txt \
|
||||
--default-library shared \
|
||||
-Diconv=disabled -Dlua=enabled \
|
||||
-Dlibmpv=true -Dcplayer=false \
|
||||
-Dmanpage-build=disabled
|
||||
|
||||
ninja -C $build -j$cores
|
||||
if [ -f $build/libmpv.a ]; then
|
||||
echo >&2 "Meson fucked up, forcing rebuild."
|
||||
$0 clean
|
||||
exec $0 build
|
||||
fi
|
||||
DESTDIR="$prefix_dir" ninja -C $build install
|
||||
24
scripts/mpv/scripts/unibreak.sh
Executable file
24
scripts/mpv/scripts/unibreak.sh
Executable file
|
|
@ -0,0 +1,24 @@
|
|||
#!/bin/bash -e
|
||||
|
||||
. ../../include/path.sh
|
||||
|
||||
build=_build$ndk_suffix
|
||||
|
||||
if [ "$1" == "build" ]; then
|
||||
true
|
||||
elif [ "$1" == "clean" ]; then
|
||||
rm -rf $build
|
||||
exit 0
|
||||
else
|
||||
exit 255
|
||||
fi
|
||||
|
||||
mkdir -p $build
|
||||
cd $build
|
||||
|
||||
../configure \
|
||||
--host=$ndk_triple --with-pic \
|
||||
--enable-static --disable-shared
|
||||
|
||||
make -j$cores
|
||||
make DESTDIR="$prefix_dir" install
|
||||
Loading…
Add table
Add a link
Reference in a new issue