diff --git a/.github/workflows/build.yaml b/.github/workflows/build.yaml index 3ce3379..84000f3 100644 --- a/.github/workflows/build.yaml +++ b/.github/workflows/build.yaml @@ -1,13 +1,6 @@ name: "Build and Test" on: push: - branches: - - '*' - tags-ignore: - - '*' - paths-ignore: - - '*.md' - - 'docs/**' pull_request: workflow_dispatch: release: @@ -16,33 +9,42 @@ permissions: pull-requests: write contents: write jobs: - linux-x86_64: + build-publish: runs-on: ubuntu-24.04 steps: - - shell: bash - name: "Install mpv" - run: | - sudo apt-get update -y - sudo apt-get install -y libmpv-dev libavformat-dev libavcodec-dev libavutil-dev libswscale-dev libavdevice-dev libavfilter-dev libswresample-dev libpostproc-dev libpipewire-0.3-dev - - uses: silenium-dev/actions/jni-natives/ubuntu@main + - uses: actions/checkout@v6 + - uses: cachix/install-nix-action@v31 with: - gradle-cache-encryption-key: ${{ secrets.GRADLE_CACHE_ENCRYPTION_KEY }} - snapshot-repo-url: "https://reposilite.silenium.dev/snapshots" - release-repo-url: "https://reposilite.silenium.dev/releases" - repo-username: ${{ secrets.REPOSILITE_USERNAME }} - repo-password: ${{ secrets.REPOSILITE_PASSWORD }} - tests: true - java-version: 11 - platform: ${{ github.job }} - kotlin: - runs-on: ubuntu-24.04 - steps: + nix_path: nixpkgs=channel:nixos-unstable + github_access_token: ${{ secrets.GITHUB_TOKEN }} + - name: Restore and save Nix store + uses: nix-community/cache-nix-action@v7 + with: + # restore and save a cache using this key + primary-key: nix-${{ runner.os }}-${{ hashFiles('**/*.nix', '**/flake.lock') }} + # if there's no cache hit, restore a cache by this prefix + restore-prefixes-first-match: nix-${{ runner.os }}- + # collect garbage until the Nix store size (in bytes) is at most this number + # before trying to save a new cache + # 1G = 1073741824 + gc-max-store-size-linux: 4G + # do purge caches + purge: true + # purge all versions of the cache + purge-prefixes: nix-${{ runner.os }}- + # created more than this number of seconds ago + purge-created: 0 + # or last accessed this duration (ISO 8601 duration format) + # before the start of the `Post Restore and save Nix store` phase + purge-last-accessed: P7D + # except any version with the key that is the same as the `primary-key` + purge-primary-key: never - uses: silenium-dev/actions/kotlin@main with: gradle-cache-encryption-key: ${{ secrets.GRADLE_CACHE_ENCRYPTION_KEY }} - snapshot-repo-url: "https://reposilite.silenium.dev/snapshots" - release-repo-url: "https://reposilite.silenium.dev/releases" - repo-username: ${{ secrets.REPOSILITE_USERNAME }} - repo-password: ${{ secrets.REPOSILITE_PASSWORD }} + snapshot-repo-url: "https://repoflow.silenium.dev/api/maven/public/maven-snapshots" + release-repo-url: "https://repoflow.silenium.dev/api/maven/public/maven-releases" + repo-username: ${{ secrets.REPOFLOW_USERNAME }} + repo-password: ${{ secrets.REPOFLOW_PASSWORD }} tests: false - java-version: 11 + java-version: 17 diff --git a/.gitignore b/.gitignore index f87be73..4f5bfca 100644 --- a/.gitignore +++ b/.gitignore @@ -15,7 +15,7 @@ rgb.png texture.png va_surface.png textures/ - +native/result ### IntelliJ IDEA ### .idea/ diff --git a/.run/desktop.run.xml b/.run/desktop.run.xml deleted file mode 100644 index 40830f1..0000000 --- a/.run/desktop.run.xml +++ /dev/null @@ -1,21 +0,0 @@ - - - - - - - true - - - diff --git a/.teamcity/settings.kts b/.teamcity/settings.kts index e51dbe2..7c90879 100644 --- a/.teamcity/settings.kts +++ b/.teamcity/settings.kts @@ -1,35 +1,18 @@ -import jetbrains.buildServer.configs.kotlin.* +import jetbrains.buildServer.configs.kotlin.BuildType import jetbrains.buildServer.configs.kotlin.buildFeatures.perfmon +import jetbrains.buildServer.configs.kotlin.buildFeatures.pullRequests +import jetbrains.buildServer.configs.kotlin.buildSteps.gradle +import jetbrains.buildServer.configs.kotlin.project import jetbrains.buildServer.configs.kotlin.projectFeatures.githubConnection +import jetbrains.buildServer.configs.kotlin.triggers.VcsTrigger import jetbrains.buildServer.configs.kotlin.triggers.vcs - -/* -The settings script is an entry point for defining a TeamCity -project hierarchy. The script should contain a single call to the -project() function with a Project instance or an init function as -an argument. - -VcsRoots, BuildTypes, Templates, and subprojects can be -registered inside the project using the vcsRoot(), buildType(), -template(), and subProject() methods respectively. - -To debug settings scripts in command-line, run the - - mvnDebug org.jetbrains.teamcity:teamcity-configs-maven-plugin:generate - -command and attach your debugger to the port 8000. - -To debug in IntelliJ Idea, open the 'Maven Projects' tool window (View --> Tool Windows -> Maven Projects), find the generate task node -(Plugins -> teamcity-configs -> teamcity-configs:generate), the -'Debug' option is available in the context menu for the task. -*/ +import jetbrains.buildServer.configs.kotlin.version version = "2025.11" project { - - buildType(Build) + buildType(BuildSnapshot) + buildType(BuildRelease) features { githubConnection { @@ -41,16 +24,65 @@ project { } } -object Build : BuildType({ - name = "Build" +abstract class Build( + val publishRepository: String, + val publishUsername: String, + val publishPassword: String, + val publishVersion: String, + val trigger: VcsTrigger.() -> Unit, +) : BuildType({ + name = "Build and Publish" triggers { vcs { + branchFilter = "+:refs/heads/*" } } features { perfmon { } + + pullRequests { + provider = github { + authType = vcsRoot() + ignoreDrafts = true + } + } + } + + requirements { + exists("system.nix.store") + } + + steps { + gradle { + tasks = "build publish" + gradleParams = "-Pdeploy.version=${publishVersion} -Pdeploy.kotlin=true" + incremental = true + param("env.MAVEN_REPO_URL", publishRepository) + param("env.MAVEN_REPO_USERNAME", publishUsername) + param("env.MAVEN_REPO_PASSWORD", publishPassword) + } } }) + +object BuildSnapshot : Build( + publishRepository = "https://repoflow.silenium.dev/api/maven/public/maven-snapshots", + publishUsername = "teamcitypublic", + publishPassword = "credentialsJSON:c8524851-3a17-4ea4-ac26-49a99c5c387c", + publishVersion = "%build.vcs.number%", + trigger = { + branchFilter = "+:refs/heads/*" + } +) + +object BuildRelease : Build( + publishRepository = "https://repoflow.silenium.dev/api/maven/public/maven-releases", + publishUsername = "teamcitypublic", + publishPassword = "credentialsJSON:c8524851-3a17-4ea4-ac26-49a99c5c387c", + publishVersion = "%build.vcs.number%", + trigger = { + branchFilter = "+:refs/tags/*" + } +) diff --git a/build-logic/build.gradle.kts b/build-logic/build.gradle.kts new file mode 100644 index 0000000..9c64596 --- /dev/null +++ b/build-logic/build.gradle.kts @@ -0,0 +1,33 @@ +plugins { + `kotlin-dsl` + `java-gradle-plugin` +} + +repositories { + maven("https://reposilite.silenium.dev/releases") { + name = "silenium-releases" + } + google() + mavenCentral() + gradlePluginPortal() +} + +dependencies { + implementation(files(libs.javaClass.superclass.protectionDomain.codeSource.location)) + api(libs.bundles.gradle.plugins) + libs.bundles.kotlin.plugins.get().forEach { + api(variantOf(provider { it }) { + classifier("gradle813") + }) + } + api(libs.jni.utils) +} + +gradlePlugin { + plugins { + register("natives") { + id = "av-natives" + implementationClass = "dev.silenium.compose.av.build.NativesPlugin" + } + } +} diff --git a/build-logic/settings.gradle.kts b/build-logic/settings.gradle.kts new file mode 100644 index 0000000..792f951 --- /dev/null +++ b/build-logic/settings.gradle.kts @@ -0,0 +1,13 @@ +dependencyResolutionManagement { + repositories { + google() + mavenCentral() + gradlePluginPortal() + } + + versionCatalogs { + create("libs") { + from(files("../gradle/libs.versions.toml")) + } + } +} diff --git a/build-logic/src/main/kotlin/dev/silenium/compose/av/build/DownloadFile.kt b/build-logic/src/main/kotlin/dev/silenium/compose/av/build/DownloadFile.kt new file mode 100644 index 0000000..ae7286a --- /dev/null +++ b/build-logic/src/main/kotlin/dev/silenium/compose/av/build/DownloadFile.kt @@ -0,0 +1,53 @@ +package dev.silenium.compose.av.build + +import org.gradle.api.logging.Logger +import org.gradle.internal.logging.progress.ProgressLogger +import java.io.OutputStream +import java.net.URI +import kotlin.time.Clock +import kotlin.time.Duration.Companion.seconds +import kotlin.time.ExperimentalTime + +@OptIn(ExperimentalTime::class) +fun downloadFile(progressLogger: ProgressLogger, logger: Logger, uri: URI, outputStream: OutputStream) { + try { + val conn = uri.toURL().openConnection() + conn.connect() + val size = conn.contentLengthLong + progressLogger.progress("${size.toMB()} MB") + + var downloaded = 0L + var lastLog = Clock.System.now() + val buf = ByteArray(1024) + conn.inputStream.use { input -> + while (true) { + if (Thread.currentThread().isInterrupted) { + throw InterruptedException("Download interrupted") + } + val read = input.read(buf) + if (read == -1) break + outputStream.write(buf, 0, read) + + downloaded += read + if (downloaded > 0) { + progressLogger.progress("${downloaded.toMB()} MB / ${size.toMB()} MB (${downloaded partOf size}%)") + } else { + progressLogger.progress("${size.toMB()} MB") + } + val now = Clock.System.now() + if (now - lastLog > 1.seconds) { + lastLog = now + logger.lifecycle("${progressLogger.description}: ${downloaded.toMB()} MB / ${size.toMB()} MB (${downloaded partOf size}%)") + } + } + } + progressLogger.completed() + logger.lifecycle("${progressLogger.description}: ${downloaded.toMB()} MB / ${size.toMB()} MB (${downloaded partOf size}%)") + } catch (e: Exception) { + progressLogger.completed(e.message ?: "Unknown error", true) + throw e + } +} + +private fun Long.toMB() = this / 1024 / 1024 +private infix fun Long.partOf(full: Long) = (this * 100) / full diff --git a/build-logic/src/main/kotlin/dev/silenium/compose/av/build/NativesExtension.kt b/build-logic/src/main/kotlin/dev/silenium/compose/av/build/NativesExtension.kt new file mode 100644 index 0000000..9c01a8d --- /dev/null +++ b/build-logic/src/main/kotlin/dev/silenium/compose/av/build/NativesExtension.kt @@ -0,0 +1,15 @@ +package dev.silenium.compose.av.build + +import org.gradle.api.file.ConfigurableFileCollection +import org.gradle.api.file.RegularFileProperty +import org.gradle.api.provider.Property + +interface NativesExtension { + val sourceFiles: ConfigurableFileCollection + val libName: Property + val libVersion: Property + + val nixFlake: RegularFileProperty + val nixFlakeLock: RegularFileProperty + val showLogs: Property +} diff --git a/build-logic/src/main/kotlin/dev/silenium/compose/av/build/NativesPlugin.kt b/build-logic/src/main/kotlin/dev/silenium/compose/av/build/NativesPlugin.kt new file mode 100644 index 0000000..1cefd54 --- /dev/null +++ b/build-logic/src/main/kotlin/dev/silenium/compose/av/build/NativesPlugin.kt @@ -0,0 +1,87 @@ +package dev.silenium.compose.av.build + +import dev.silenium.libs.jni.NativeLoader +import dev.silenium.libs.jni.Platform +import org.gradle.api.JavaVersion +import org.gradle.api.Plugin +import org.gradle.api.Project +import org.gradle.api.plugins.JavaPluginExtension +import org.gradle.api.tasks.Delete +import org.gradle.kotlin.dsl.apply +import org.gradle.kotlin.dsl.configure +import org.gradle.kotlin.dsl.named +import org.gradle.kotlin.dsl.register +import org.gradle.language.jvm.tasks.ProcessResources +import org.jetbrains.kotlin.gradle.dsl.JvmTarget +import org.jetbrains.kotlin.gradle.dsl.KotlinJvmExtension +import org.jetbrains.kotlin.gradle.plugin.KotlinPluginWrapper + +@Suppress("unused") // used as plugin entrypoint +class NativesPlugin : Plugin { + override fun apply(target: Project) { + target.apply() + target.configure { + compilerOptions.jvmTarget.set(JvmTarget.JVM_11) + } + target.configure { + sourceCompatibility = JavaVersion.VERSION_11 + targetCompatibility = JavaVersion.VERSION_11 + } + + val ext = target.extensions.create("natives", NativesExtension::class.java) + ext.libVersion.convention(target.version.toString()) + ext.libName.convention(target.name) + ext.nixFlakeLock.convention(target.layout.file(ext.nixFlake.map { it.asFile.resolveSibling("flake.lock") })) + val nixResultDir = target.layout.buildDirectory.dir("nix-result") + + val nixClean = target.tasks.register("nixClean") { + delete(nixResultDir) + } + target.tasks.named("clean").configure { + dependsOn(nixClean) + } + + val nixBuild = target.tasks.register("nixBuild") { + doFirst { + nixResultDir.get().asFile.deleteRecursively() + } + group = "build" + inputs.files(ext.sourceFiles) + inputs.files(ext.nixFlake, ext.nixFlakeLock) + libName.set(ext.libName) + resultDir.set(nixResultDir) + showLogs.set(ext.showLogs) + } + + target.afterEvaluate { + tasks.named("processResources") { + val out = nixBuild.flatMap { it.resultDir.asFile } + val targets = mapOf( + Platform(Platform.OS.LINUX, Platform.Arch.X86_64) to "x86_64-linux", + Platform(Platform.OS.LINUX, Platform.Arch.ARM64) to "aarch64-linux", + Platform(Platform.OS.WINDOWS, Platform.Arch.X86_64) to "x86_64-windows", + ) + targets.forEach { (platform, target) -> + val src = out.map { out -> + out.resolve("${ext.libName.get()}-${target}-${ext.libVersion.get()}").resolve("lib") + } + val platformFormat = NativeLoader.fileNameTemplate(platform) + from(src) { + include("*.so") + include("*.dll") + include("*.dylib") + rename { + val matches = platformFormat + .replace(".", "\\.") + .replace("%s", "(.*)") + .toRegex() + .find(it) ?: error("library name does not match platform pattern: $it") + + NativeLoader.libPath(matches.groupValues[1], platform = platform) + } + } + } + } + } + } +} diff --git a/build-logic/src/main/kotlin/dev/silenium/compose/av/build/NixBuildTask.kt b/build-logic/src/main/kotlin/dev/silenium/compose/av/build/NixBuildTask.kt new file mode 100644 index 0000000..c95553f --- /dev/null +++ b/build-logic/src/main/kotlin/dev/silenium/compose/av/build/NixBuildTask.kt @@ -0,0 +1,28 @@ +package dev.silenium.compose.av.build + +import org.gradle.api.file.DirectoryProperty +import org.gradle.api.provider.Property +import org.gradle.api.tasks.Exec +import org.gradle.api.tasks.Input +import org.gradle.api.tasks.OutputDirectory + +// Never cache as read-only outputs break cache restore +// Caching will instead happen on nix level +abstract class NixBuildTask : Exec() { + @get:Input + abstract val libName: Property + + @get:Input + abstract val showLogs: Property + + @get:OutputDirectory + abstract val resultDir: DirectoryProperty + + override fun exec() { + val logArgs = arrayOf("--print-build-logs").takeIf { showLogs.get() } ?: arrayOf() + commandLine("nix", "build", "-o", resultDir.get().asFile.absolutePath, *logArgs, ".#${libName.get()}") + standardOutput = System.out + errorOutput = System.out + super.exec() + } +} diff --git a/build-logic/src/main/kotlin/dev/silenium/compose/av/build/ProjectExtensions.kt b/build-logic/src/main/kotlin/dev/silenium/compose/av/build/ProjectExtensions.kt new file mode 100644 index 0000000..13291e6 --- /dev/null +++ b/build-logic/src/main/kotlin/dev/silenium/compose/av/build/ProjectExtensions.kt @@ -0,0 +1,7 @@ +package dev.silenium.compose.av.build + +import org.gradle.accessors.dm.LibrariesForLibs +import org.gradle.api.Project +import org.gradle.kotlin.dsl.the + +val Project.libs get() = the() diff --git a/build.gradle.kts b/build.gradle.kts index cec5232..4a4ac09 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -1,4 +1,3 @@ -import org.jetbrains.compose.desktop.application.dsl.TargetFormat import org.jetbrains.gradle.ext.ProjectSettings import org.jetbrains.gradle.ext.TaskTriggersConfig import org.jetbrains.kotlin.gradle.dsl.JvmTarget @@ -12,59 +11,29 @@ plugins { `maven-publish` } -val deployNative = (findProperty("deploy.native") as String?)?.toBoolean() ?: true val deployKotlin = (findProperty("deploy.kotlin") as String?)?.toBoolean() ?: true -val skikoEGL = (findProperty("skiko.egl") as String?)?.toBoolean() ?: false dependencies { - // Note, if you develop a library, you should use compose.desktop.common. - // compose.desktop.currentOs should be used in launcher-sourceSet - // (in a separate module for demo project and in testMain). - // With compose.desktop.common you will also lose @Preview functionality - implementation(compose.desktop.currentOs) - implementation(compose.material3) - implementation(compose.materialIconsExtended) - implementation("androidx.annotation:annotation-jvm:1.9.1") + implementation(libs.compose.desktop.common) + implementation(libs.compose.material3) + implementation(libs.compose.material.icons.extended) + implementation(libs.androidx.annotation) implementation(libs.compose.gl) implementation(libs.compose.gl.natives) implementation(libs.jni.utils) implementation(libs.jna) implementation(libs.bundles.kotlinx) implementation(libs.slf4j.api) // for logging - if (deployNative) { - implementation(project(":native")) - } + implementation(project(":native")) implementation(kotlin("reflect")) - if (skikoEGL) { - implementation(libs.bundles.skiko) { - version { - strictly(libs.skiko.awt.runtime.linux.x64.get().version!!) - } - } - } - testImplementation(compose.materialIconsExtended) + testImplementation(compose.desktop.currentOs) + testImplementation(libs.compose.material.icons.extended) testImplementation(libs.bundles.kotest) testImplementation(libs.mockk) testImplementation(libs.logback.classic) } -configurations.all { - resolutionStrategy.cacheChangingModulesFor(0, TimeUnit.SECONDS) -} - -compose.desktop { - application { - mainClass = "MainKt" - - nativeDistributions { - targetFormats(TargetFormat.Dmg, TargetFormat.Msi, TargetFormat.Deb) - packageName = "gl-demo" - packageVersion = "1.0.0" - } - } -} - val templateSrc = layout.projectDirectory.dir("src/main/templates") val templateDst = layout.buildDirectory.dir("generated/templates") val templateProps = mapOf( @@ -96,10 +65,8 @@ tasks { kotlin { compilerOptions { - freeCompilerArgs.add("-Xcontext-receivers") - jvmTarget = JvmTarget.JVM_11 + jvmTarget = JvmTarget.JVM_17 } - jvmToolchain(11) } sourceSets.main { @@ -109,6 +76,9 @@ sourceSets.main { } java { + sourceCompatibility = kotlin.compilerOptions.jvmTarget.map { JavaVersion.toVersion(it.target) }.get() + targetCompatibility = sourceCompatibility + withSourcesJar() withJavadocJar() } diff --git a/gradle.properties b/gradle.properties index 815b4ff..39eede6 100644 --- a/gradle.properties +++ b/gradle.properties @@ -1,3 +1,4 @@ org.gradle.jvmargs=-Xmx2048m -Dfile.encoding=UTF-8 kotlin.code.style=official -skiko.egl=false +org.gradle.configuration-cache=true +org.gradle.caching=true diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 2e0310c..53db166 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -1,23 +1,22 @@ [versions] -kotlin = "2.1.0" +kotlin = "2.3.10" -kotlinx-coroutines = "1.10.1" -kotlinx-serialization = "1.8.0" -kotlinx-datetime = "0.6.1" +kotlinx-coroutines = "1.10.2" +kotlinx-serialization = "1.10.0" +kotlinx-datetime = "0.7.1" -compose = "1.7.3" -compose-gl = "0.7.4" -jni-utils = "0.1.6" -skiko = "0.8.19-egl" -jna = "5.16.0" +compose = "1.11.0-alpha02" +compose-gl = "0.9.1" +jni-utils = "0.1.8" +jna = "5.18.1" -slf4j = "2.0.16" -logback = "1.5.16" +slf4j = "2.0.17" +logback = "1.5.32" -kotest = "5.9.1" -mockk = "1.13.14" +kotest = "6.1.3" +mockk = "1.14.9" -idea-ext = "1.1.9" +idea-ext = "1.4.1" [libraries] kotlinx-coroutines-core = { group = "org.jetbrains.kotlinx", name = "kotlinx-coroutines-core", version.ref = "kotlinx-coroutines" } @@ -32,16 +31,18 @@ kotlinx-serialization-json = { group = "org.jetbrains.kotlinx", name = "kotlinx- kotlinx-datetime = { group = "org.jetbrains.kotlinx", name = "kotlinx-datetime", version.ref = "kotlinx-datetime" } +compose-desktop-common = { group = "org.jetbrains.compose.desktop", name = "desktop", version.ref = "compose" } +compose-material3 = { group = "org.jetbrains.compose.material3", name = "material3", version.ref = "compose" } +compose-material-icons-extended = { group = "org.jetbrains.compose.material", name = "material-icons-extended", version = "1.7.3" } +androidx-annotation = { group = "androidx.annotation", name = "annotation", version = "1.9.1" } + compose-gl = { group = "dev.silenium.compose.gl", name = "compose-gl", version.ref = "compose-gl" } -compose-gl-natives = { group = "dev.silenium.compose.gl", name = "compose-gl-natives-linux-x86_64", version.ref = "compose-gl" } +compose-gl-natives = { group = "dev.silenium.compose.gl", name = "compose-gl-natives-all", version.ref = "compose-gl" } ffmpeg-natives = { group = "dev.silenium.libs", name = "ffmpeg-natives", version = "7.1+0.2.0" } mpv-natives = { group = "dev.silenium.libs", name = "ffmpeg-natives", version = "0.39.0+0.1.2" } jni-utils = { group = "dev.silenium.libs.jni", name = "jni-utils", version.ref = "jni-utils" } jna = { group = "net.java.dev.jna", name = "jna", version.ref = "jna" } -skiko-awt = { group = "org.jetbrains.skiko", name = "skiko-awt", version.ref = "skiko" } -skiko-awt-runtime-linux-x64 = { group = "org.jetbrains.skiko", name = "skiko-awt-runtime-linux-x64", version.ref = "skiko" } - slf4j-api = { group = "org.slf4j", name = "slf4j-api", version.ref = "slf4j" } logback-classic = { group = "ch.qos.logback", name = "logback-classic", version.ref = "logback" } @@ -52,6 +53,12 @@ kotest-assertions-json = { group = "io.kotest", name = "kotest-assertions-json", mockk = { group = "io.mockk", name = "mockk", version.ref = "mockk" } +compose-gradle-plugin = { group = "org.jetbrains.compose", name = "compose-gradle-plugin", version.ref = "compose" } +kotlin-jvm-gradle-plugin = { group = "org.jetbrains.kotlin", name = "kotlin-gradle-plugin", version.ref = "kotlin" } +kotlin-compose-gradle-plugin = { group = "org.jetbrains.kotlin", name = "compose-compiler-gradle-plugin", version.ref = "kotlin" } +kotlin-serialization-gradle-plugin = { group = "org.jetbrains.kotlin", name = "kotlin-serialization", version.ref = "kotlin" } +idea-ext = { group = "org.jetbrains.gradle.plugin.idea-ext", name = "org.jetbrains.gradle.plugin.idea-ext.gradle.plugin", version.ref = "idea-ext" } + [plugins] kotlin = { id = "org.jetbrains.kotlin.jvm", version.ref = "kotlin" } kotlin-serialization = { id = "org.jetbrains.kotlin.plugin.serialization", version.ref = "kotlin" } @@ -79,7 +86,12 @@ kotest = [ "kotest-assertions-json", ] -skiko = [ - "skiko-awt", - "skiko-awt-runtime-linux-x64", +kotlin-plugins = [ + "kotlin-jvm-gradle-plugin", + "kotlin-compose-gradle-plugin", + "kotlin-serialization-gradle-plugin", +] +gradle-plugins = [ + "compose-gradle-plugin", + "idea-ext", ] diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index 1e2fbf0..2f2958b 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,5 +1,5 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-8.10.2-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-9.3.1-bin.zip zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists diff --git a/native/.gitignore b/native/.gitignore index db5c42d..c4988be 100644 --- a/native/.gitignore +++ b/native/.gitignore @@ -1,3 +1,5 @@ .idea/ *.iml -cmake-build-*/ \ No newline at end of file +cmake-build-*/ +/subprojects +buildDir/ diff --git a/native/CMakeLists.txt b/native/CMakeLists.txt deleted file mode 100644 index 4c82b27..0000000 --- a/native/CMakeLists.txt +++ /dev/null @@ -1,97 +0,0 @@ -cmake_minimum_required(VERSION 3.16) -if (NOT DEFINED PROJECT_NAME) - set(PROJECT_NAME "compose-av") -endif () -if (NOT DEFINED NATIVE_PLATFORM) - message(FATAL_ERROR "NATIVE_PLATFORM must be defined") -else () - message(STATUS "NATIVE_PLATFORM: ${NATIVE_PLATFORM}") -endif () -if (NOT DEFINED FFMPEG_VERSION) - message(FATAL_ERROR "FFMPEG_VERSION must be defined") -else () - message(STATUS "FFMPEG_VERSION: ${FFMPEG_VERSION}") -endif () -if (NOT DEFINED MPV_VERSION) - message(FATAL_ERROR "MPV_VERSION must be defined") -else () - message(STATUS "MPV_VERSION: ${MPV_VERSION}") -endif () -if (NOT DEFINED JAVA_HOME) - message(FATAL_ERROR "JAVA_HOME must be defined") -else () - message(STATUS "JAVA_HOME: ${JAVA_HOME}") -endif () - -project(${PROJECT_NAME} LANGUAGES CXX) - -set(CMAKE_CXX_STANDARD 20) -set(CMAKE_CXX_STANDARD_REQUIRED ON) -set(CMAKE_POSITION_INDEPENDENT_CODE ON) -option(USE_SYSTEM_FFMPEG "Link dynamically against system ffmpeg instead of using static prebuilt libraries" OFF) -option(USE_SYSTEM_MPV "Link dynamically against system mpv instead of using static prebuilt libraries" OFF) - -include(ExternalProject) -if (NOT USE_SYSTEM_FFMPEG) - include(thirdparty/ffmpeg.cmake) -endif () -if (NOT USE_SYSTEM_MPV) - include(thirdparty/mpv.cmake) -endif () - -set(SOURCES - src/cpp/util/Errors.cpp - src/cpp/helper/errors.hpp - src/cpp/helper/errors.cpp - src/cpp/mpv/MPV.cpp -) - -if (CMAKE_SYSTEM_NAME STREQUAL "Linux") - if (NOT DEFINED JAVA_HOME) - set(JAVA_HOME "/usr/lib/jvm/java-11-openjdk") - endif () - -# list(APPEND SOURCES) -elseif (CMAKE_SYSTEM_NAME STREQUAL "Windows") -endif () - -add_library(${PROJECT_NAME} SHARED ${SOURCES}) -target_include_directories(${PROJECT_NAME} PRIVATE "src/cpp/") - -message(STATUS "JAVA_HOME: ${JAVA_HOME}") -target_include_directories(${PROJECT_NAME} PUBLIC "${JAVA_HOME}/include") -if (NOT USE_SYSTEM_FFMPEG) - target_link_libraries(${PROJECT_NAME} PUBLIC -Wl,--push-state,--whole-archive,--allow-multiple-definition ffmpeg -Wl,--pop-state) -endif () -if (NOT USE_SYSTEM_MPV) - target_link_libraries(${PROJECT_NAME} PUBLIC -Wl,--push-state,--whole-archive,--allow-multiple-definition mpv -Wl,--pop-state) -endif () -add_custom_command(TARGET ${PROJECT_NAME} POST_BUILD COMMAND objcopy --localize-hidden --strip-all --strip-unneeded $) - -if (CMAKE_SYSTEM_NAME STREQUAL "Linux") - target_compile_definitions(${PROJECT_NAME} PRIVATE -D_LINUX) - find_package(PkgConfig REQUIRED) - pkg_check_modules(GL REQUIRED IMPORTED_TARGET egl glx) - target_link_libraries(${PROJECT_NAME} PUBLIC PkgConfig::GL) - target_include_directories(${PROJECT_NAME} PUBLIC "${JAVA_HOME}/include/linux") - add_compile_options(-static -static-libstdc++ -static-libgcc) - add_link_options(-static -static-libstdc++ -static-libgcc) - if (USE_SYSTEM_FFMPEG) - pkg_check_modules(FFMPEG REQUIRED IMPORTED_TARGET libavcodec libavformat libavutil libswscale libavfilter) - target_link_libraries(${PROJECT_NAME} PRIVATE PkgConfig::FFMPEG) - endif () - if (USE_SYSTEM_MPV) - pkg_check_modules(MPV REQUIRED IMPORTED_TARGET mpv) - target_link_libraries(${PROJECT_NAME} PRIVATE PkgConfig::MPV) - endif () -elseif (CMAKE_SYSTEM_NAME STREQUAL "Windows") - target_compile_definitions(${PROJECT_NAME} PRIVATE -D_WINDOWS) - target_link_libraries(${PROJECT_NAME} PUBLIC opengl32 dxgi d3d11 d3dcompiler) - target_include_directories(${PROJECT_NAME} PUBLIC "${JAVA_HOME}/include/win32") -endif () - -#add_executable(test main.cpp) -#target_link_libraries(test PRIVATE PkgConfig::FFMPEG) -#target_link_libraries(test PRIVATE ${PROJECT_NAME}) -#target_link_libraries(test PRIVATE X11 GLX xcb va va-glx va-x11) -#target_include_directories(test PRIVATE "src/cpp/") diff --git a/native/build.gradle.kts b/native/build.gradle.kts index 4129e56..0cbd48f 100644 --- a/native/build.gradle.kts +++ b/native/build.gradle.kts @@ -1,105 +1,22 @@ -import dev.silenium.libs.jni.NativeLoader -import dev.silenium.libs.jni.NativePlatform -import dev.silenium.libs.jni.Platform - -buildscript { - repositories { - maven("https://reposilite.silenium.dev/releases") { - name = "silenium-releases" - } - } - dependencies { - classpath(libs.jni.utils) - } -} - plugins { - alias(libs.plugins.kotlin) - `maven-publish` -} - -val libName = rootProject.name -val deployNative = (findProperty("deploy.native") as String?)?.toBoolean() ?: true - -val withGPL: Boolean = findProperty("ffmpeg.gpl").toString().toBoolean() -val platformExtension = "-gpl".takeIf { withGPL }.orEmpty() -val platformString = findProperty("ffmpeg.platform")?.toString() -val platform = platformString?.let { Platform(it, platformExtension) } ?: NativePlatform.platform(platformExtension) - -val cmakeExe = findProperty("cmake.executable") as? String ?: "cmake" -val generateMakefile = tasks.register("generateMakefile") { - workingDir = layout.buildDirectory.dir("cmake").get().asFile.apply { mkdirs() } - val additionalFlags = mutableListOf( - "-DJAVA_HOME=${System.getProperty("java.home")}", - "-DPROJECT_NAME=${libName}", - "-DNATIVE_PLATFORM=${platform.osArch}", - "-DFFMPEG_PLATFORM_EXTENSION=${platform.extension}", - "-DFFMPEG_VERSION=${libs.ffmpeg.natives.get().version}", - "-DMPV_VERSION=${libs.mpv.natives.get().version}", - ) - commandLine( - cmakeExe, - *additionalFlags.toTypedArray(), - layout.projectDirectory.asFile.absolutePath, - ) - - inputs.file(layout.projectDirectory.file("CMakeLists.txt")) - inputs.properties( - "JAVA_HOME" to System.getProperty("java.home"), - "PROJECT_NAME" to libName, - "NATIVE_PLATFORM" to platform.osArch, - "FFMPEG_PLATFORM_EXTENSION" to platform.extension, - "FFMPEG_VERSION" to libs.ffmpeg.natives.get().version, - "MPV_VERSION" to libs.mpv.natives.get().version, - ) - outputs.dir(workingDir) - standardOutput = System.out -} - -val compileNative = tasks.register("compileNative") { - workingDir = layout.buildDirectory.dir("cmake").get().asFile - commandLine(cmakeExe, "--build", ".", "-j", Runtime.getRuntime().availableProcessors().toString()) - dependsOn(generateMakefile) - - standardOutput = System.out - val fileNameTemplate = NativeLoader.fileNameTemplate(platform) - when (platform.os) { - Platform.OS.WINDOWS -> { - outputs.files(layout.buildDirectory.file("cmake/Debug/${fileNameTemplate.format(libName)}")) - } - - Platform.OS.LINUX, Platform.OS.DARWIN -> { - outputs.files(layout.buildDirectory.file("cmake/${fileNameTemplate.format(libName)}")) - } - } - inputs.file(layout.buildDirectory.file("cmake/CMakeCache.txt")) - inputs.dir(layout.projectDirectory.dir("src")) - inputs.file(layout.projectDirectory.file("CMakeLists.txt")) - outputs.cacheIf { true } + id("av-natives") } -tasks.processResources { - dependsOn(compileNative) - // Required for configuration cache - val libName = rootProject.name - val platformString = findProperty("ffmpeg.platform")?.toString() - val withGPL: Boolean = findProperty("ffmpeg.gpl").toString().toBoolean() - val platformExtension = "-gpl".takeIf { withGPL }.orEmpty() - val platform = platformString?.let { Platform(it, platformExtension) } ?: NativePlatform.platform(platformExtension) +val deployKotlin = (findProperty("deploy.kotlin") as String?)?.toBoolean() ?: true - from(compileNative.get().outputs.files) { - rename { - NativeLoader.libPath(libName, platform = platform) - } - } +natives { + libName = rootProject.name + libVersion = "0.1.0" + nixFlake = file("flake.nix") + sourceFiles.from("src", "meson.build", "subprojects.tpl") + showLogs = providers.environmentVariable("CI").orElse("false").map { it != "false" } } publishing { publications { - if (deployNative) { - create("natives${platform.capitalized}") { - from(components["java"]) - artifactId = "$libName-natives-$platform" + if (deployKotlin) { + create("maven") { + from(components["kotlin"]) } } } diff --git a/native/flake.lock b/native/flake.lock new file mode 100644 index 0000000..6931c16 --- /dev/null +++ b/native/flake.lock @@ -0,0 +1,97 @@ +{ + "nodes": { + "flake-parts": { + "inputs": { + "nixpkgs-lib": "nixpkgs-lib" + }, + "locked": { + "lastModified": 1772408722, + "narHash": "sha256-rHuJtdcOjK7rAHpHphUb1iCvgkU3GpfvicLMwwnfMT0=", + "owner": "hercules-ci", + "repo": "flake-parts", + "rev": "f20dc5d9b8027381c474144ecabc9034d6a839a3", + "type": "github" + }, + "original": { + "owner": "hercules-ci", + "repo": "flake-parts", + "type": "github" + } + }, + "jni-utils": { + "inputs": { + "flake-parts": "flake-parts", + "nixpkgs": "nixpkgs" + }, + "locked": { + "lastModified": 1772752807, + "narHash": "sha256-/j59dcF9R1pwmBLTnODWx88kS+sp+92fnAb7InVYMEM=", + "owner": "silenium-dev", + "repo": "jni-utils", + "rev": "a7698b4c08eaa05bc2f490c911bc9b1f111e4d2c", + "type": "github" + }, + "original": { + "owner": "silenium-dev", + "ref": "nix-base", + "repo": "jni-utils", + "type": "github" + } + }, + "nixpkgs": { + "locked": { + "lastModified": 1771848320, + "narHash": "sha256-0MAd+0mun3K/Ns8JATeHT1sX28faLII5hVLq0L3BdZU=", + "owner": "nixos", + "repo": "nixpkgs", + "rev": "2fc6539b481e1d2569f25f8799236694180c0993", + "type": "github" + }, + "original": { + "owner": "nixos", + "ref": "nixos-unstable", + "repo": "nixpkgs", + "type": "github" + } + }, + "nixpkgs-lib": { + "locked": { + "lastModified": 1772328832, + "narHash": "sha256-e+/T/pmEkLP6BHhYjx6GmwP5ivonQQn0bJdH9YrRB+Q=", + "owner": "nix-community", + "repo": "nixpkgs.lib", + "rev": "c185c7a5e5dd8f9add5b2f8ebeff00888b070742", + "type": "github" + }, + "original": { + "owner": "nix-community", + "repo": "nixpkgs.lib", + "type": "github" + } + }, + "nixpkgs_2": { + "locked": { + "lastModified": 1772624091, + "narHash": "sha256-QKyJ0QGWBn6r0invrMAK8dmJoBYWoOWy7lN+UHzW1jc=", + "owner": "nixos", + "repo": "nixpkgs", + "rev": "80bdc1e5ce51f56b19791b52b2901187931f5353", + "type": "github" + }, + "original": { + "owner": "nixos", + "ref": "nixos-unstable", + "repo": "nixpkgs", + "type": "github" + } + }, + "root": { + "inputs": { + "jni-utils": "jni-utils", + "nixpkgs": "nixpkgs_2" + } + } + }, + "root": "root", + "version": 7 +} diff --git a/native/flake.nix b/native/flake.nix new file mode 100644 index 0000000..ee23a5a --- /dev/null +++ b/native/flake.nix @@ -0,0 +1,212 @@ +{ + description = "jni build environment"; + + inputs = { + nixpkgs.url = "github:nixos/nixpkgs?ref=nixos-unstable"; + jni-utils.url = "github:silenium-dev/jni-utils?ref=nix-base"; + }; + + outputs = { nixpkgs, jni-utils, ... }: + let + pkgs = nixpkgs.legacyPackages."x86_64-linux"; + fs = nixpkgs.lib.fileset; + in + { + packages."x86_64-linux" = jni-utils.lib.buildJNILib { + name = "compose-av"; + version = "0.1.0"; + mesonTarget = "compose-av"; + buildType = "release"; + libName = "compose-av"; + libDir = "src"; + + additionalNativeInputs = targetSystem: pkgs: [ pkgs.p7zip pkgs.curl pkgs.cacert pkgs.python3 ]; + additionalInputs = targetSystem: pkgs: + if targetSystem == "x86_64-windows" then [ ] + else [ pkgs.libxcb pkgs.libxau pkgs.libxdmcp ]; + sources = targetSystem: + let + mpv = import ./nix/mpv-config.nix { inherit pkgs; }; + ffmpeg = import ./nix/ffmpeg-config.nix { inherit pkgs; }; + sourceFiles = fs.unions [ + ./src + ./meson.build + ./subprojects.tpl + ]; + in + (if targetSystem == "x86_64-windows" then + [ + (builtins.fetchurl { + url = mpv.${targetSystem}.source_url; + sha256 = mpv.${targetSystem}.source_hash; + name = "mpv.7z"; + }) + ] + else [ + (import ./nix/linux-sources.nix { inherit pkgs; }) + (builtins.fetchurl { + url = ffmpeg.${targetSystem}.source_url; + sha256 = ffmpeg.${targetSystem}.source_hash; + name = "ffmpeg.tar.xz"; + }) + ]) ++ [ + (builtins.path { + path = fs.toSource { + root = ./.; + fileset = sourceFiles; + }; + name = "compose-av"; + }) + ]; + + unpack = targetSystem: '' + runHook preUnpack + + # Process each source + for src in $srcs; do + srcName=$(stripHash "$src") + + case "$src" in + *.tar.gz|*.tar.xz) + dirName="''${srcName%.tar.*}" + echo "Extracting tar archive into: $dirName" + mkdir -p "$dirName" + tar xf "$src" -C "$dirName" + + # Check if archive only contained one directory + shopt -s nullglob dotglob + contents=("$dirName"/*) + if [ ''${#contents[@]} -eq 1 ] && [ -d "''${contents[0]}" ]; then + echo "Flattening single directory structure" + mv "''${contents[0]}"/* "$dirName"/ + rmdir "''${contents[0]}" + fi + + chmod -R +w "$dirName" + + for i in "$dirName"/**/*.py "$dirName"/*.py; do + echo "patching shebangs in $i" + patchShebangs --build "$i" + done + + tar cf "$dirName.tar" -C "$dirName" . + rm -rf "$dirName" + ;; + *.zip) + dirName="''${srcName%.zip}" + echo "Extracting tar archive into: $dirName" + mkdir -p "$dirName" + 7z x -o"$dirName" "$src" + + # Check if archive only contained one directory + shopt -s nullglob dotglob + contents=("$dirName"/*) + if [ ''${#contents[@]} -eq 1 ] && [ -d "''${contents[0]}" ]; then + echo "Flattening single directory structure" + mv "''${contents[0]}"/* "$dirName"/ + rmdir "''${contents[0]}" + fi + + chmod -R +w "$dirName" + + for i in "$dirName"/**/*.py "$dirName"/*.py; do + echo "patching shebangs in $i" + patchShebangs --build "$i" + done + + tar cf "$dirName.tar" -C "$dirName" . + rm -rf "$dirName" + ;; + *.7z) + dirName="''${srcName%.7z}" + echo "Extracting 7z archive into: $dirName" + mkdir -p "$dirName" + 7z x -o"$dirName" "$src" + + chmod -R +w "$dirName" + tar cf "$dirName.tar" -C "$dirName" . + ;; + *compose-av) + echo "Copying: $srcName" + cp -r "$src" "$srcName" + chmod -R +w "$srcName" + ;; + *) + dirName="''${srcName}" + echo "Compressing: $srcName" + cp -r "$src" "$srcName" + chmod -R +w "$srcName" + + for i in "$dirName"/**/*.py "$dirName"/*.py; do + echo "patching shebangs in $i" + patchShebangs --build "$i" + done + + tar cf "$dirName.tar" -C "$srcName" . + rm -rf "$srcName" + ;; + esac + done + + runHook postUnpack + ''; + postUnpackPhase = targetSystem: '' + shopt -s globstar + case "${targetSystem}" in + *-windows) + mkdir -p compose-av/subprojects/mpv + tar xf mpv.tar -C compose-av/subprojects/mpv/ + cp compose-av/subprojects.tpl/windows/packagefiles/mpv/* compose-av/subprojects/mpv/ + ;; + *-linux) + for p in ./*-patch.tar; do + echo "patching ''${p%-patch.tar}" + tar --concatenate --file="''${p%-patch.tar}.tar" "$p" + rm "$p" + done + + mkdir -p compose-av/subprojects/packagefiles/mpv/subprojects/packagefiles/libass/subprojects/packagefiles + mkdir -p compose-av/subprojects/packagefiles/mpv/subprojects/packagefiles/libass/subprojects/packagefiles/libfontconfig/subprojects/packagefiles + mv libfontconfig.tar compose-av/subprojects/packagefiles/mpv/subprojects/packagefiles/libass/subprojects/packagefiles + mv libxml2.tar compose-av/subprojects/packagefiles/mpv/subprojects/packagefiles/libass/subprojects/packagefiles/libfontconfig/subprojects/packagefiles + mv gperf.tar compose-av/subprojects/packagefiles/mpv/subprojects/packagefiles/libass/subprojects/packagefiles/libfontconfig/subprojects/packagefiles + + mkdir -p compose-av/subprojects/packagefiles/mpv/subprojects/packagefiles/harfbuzz/subprojects/packagefiles + mv icu.tar compose-av/subprojects/packagefiles/mpv/subprojects/packagefiles/harfbuzz/subprojects/packagefiles + + mv mpv.tar compose-av/subprojects/packagefiles + + mv *.tar compose-av/subprojects/packagefiles/mpv/subprojects/packagefiles + + for i in compose-av/subprojects.tpl/linux/**/*; do + if [ -d "$i" ]; then + echo "creating dir $i" + mkdir -p "compose-av/subprojects/''${i#compose-av/subprojects.tpl/linux/}" + else + echo "Expanding $i:" + cat "$i" \ + | sed 's,''${cav_archive_base},'"$(pwd)"',g' \ + > "compose-av/subprojects/''${i#compose-av/subprojects.tpl/linux/}" + fi + done + ;; + esac + + sourceRoot="$(pwd)/compose-av" + echo "changing source root: $sourceRoot" + cd "$sourceRoot" + ''; + + postInstallPhase = targetSystem: '' + case "${targetSystem}" in + *-linux) + cp -d subprojects/ffmpeg/lib/{libavcodec,libavdevice,libavfilter,libavformat,libavutil,libswresample,libswscale}.so* $out/lib + ;; + *-windows) + cp subprojects/mpv/libmpv-2.dll $out/lib + ;; + esac + ''; + }; + }; +} diff --git a/native/hack/assemble-deps.sh b/native/hack/assemble-deps.sh new file mode 100644 index 0000000..38be9c7 --- /dev/null +++ b/native/hack/assemble-deps.sh @@ -0,0 +1,74 @@ +#!/usr/bin/env bash + +function assemble_deps() { + local wrap_file="$1" + depName=$(basename "$wrap_file" .wrap) + dir=$(dirname "$wrap_file") + wrap_type=$(grep -oP '(?<=\[wrap-)[^\]]+(?=\])' "$wrap_file") + case "$wrap_type" in + file) + file_url=$(grep -oP '(?<=source_url = )[^\n]+' "$wrap_file") + file_name=$(grep -oP '(?<=source_filename = )[^\n]+' "$wrap_file") + file_hash=sha256:$(grep -oP '(?<=source_hash = )[^\n]+' "$wrap_file") + if ! file_hash_nix=$(nix hash convert "$file_hash" 2>/dev/null); then + return + fi + file_ext="${file_name##*.}" + cat </dev/null) + if [ -z "$patch_hash_nix" ]; then + echo "Failed to convert patch hash: $patch_hash" + return + fi + cat <=1.2.0', +) + +add_global_arguments('-fPIC', language : 'c') +add_global_arguments('-fPIC', language : 'cpp') +add_global_link_arguments( + language : 'cpp', +) + +mpv = subproject('mpv', + default_options : { + 'libmpv' : true, + 'drm' : 'enabled', + 'openal' : 'enabled', + 'wrap_mode' : 'forcefallback', + 'default_library' : 'static', + 'prefer_static' : true, + } +) +subdir('src') diff --git a/native/nix/ffmpeg-config.nix b/native/nix/ffmpeg-config.nix new file mode 100644 index 0000000..a0d1d4a --- /dev/null +++ b/native/nix/ffmpeg-config.nix @@ -0,0 +1,20 @@ +{ pkgs +}: +let + ffmpeg = { arch, hash }: rec { + source_url = "https://repoflow.silenium.dev/api/universal/personal/github-releases/BtbN/FFmpeg-Builds/autobuild-2026-02-25-13-05/ffmpeg-n8.0.1-64-g15504610b0-${arch}-gpl-shared-8.0.tar.xz"; + source_hash = hash; + source_filename = pkgs.lib.lists.last (pkgs.lib.strings.split "/" source_url); + directory = builtins.elemAt (pkgs.lib.strings.split "." source_filename) 0; + }; +in +{ + "x86_64-linux" = ffmpeg { + arch = "linux64"; + hash = "sha256-uwnRytgBbJLjzjI0gRRPGL70H/1d6B76QaHQcdvLtS8="; + }; + "aarch64-linux" = ffmpeg { + arch = "linuxarm64"; + hash = "sha256-tK1z9Uenfae52UZhliptXTEqlF9Lyz+Cx5CycwzixS8="; + }; +} diff --git a/native/nix/linux-sources.nix b/native/nix/linux-sources.nix new file mode 100644 index 0000000..eb4f027 --- /dev/null +++ b/native/nix/linux-sources.nix @@ -0,0 +1,170 @@ +{ pkgs }: [ + (pkgs.fetchurl { + name = "expat.tar.xz"; + url = "https://github.com/libexpat/libexpat/releases/download/R_2_7_3/expat-2.7.3.tar.xz"; + hash = "sha256-cd+PQHBqe7CoClNnB56nXZHaT4xlxY7Fm837997Nq58="; + }) + (pkgs.fetchurl { + name = "expat-patch.zip"; + url = "https://wrapdb.mesonbuild.com/v2/expat_2.7.3-1/get_patch"; + hash = "sha256-6HDrSy48FCzh0Y7SQwmqQOB8bJEaRiX6HG/Ek7oYFTo="; + }) + + (pkgs.fetchgit { + name = "fmt"; + url = "https://github.com/fmtlib/fmt.git"; + rev = "12.0.0"; + hash = "sha256-AZDmIeU1HbadC+K0TIAGogvVnxt0oE9U6ocpawIgl6g="; + deepClone = false; + }) + + (pkgs.fetchgit { + name = "freetype2"; + url = "https://gitlab.freedesktop.org/freetype/freetype.git"; + rev = "VER-2-13-3"; + hash = "sha256-fAefpGvNWM9t8XPS8HOCmXLO/76pwp2TV5yHDSfjc/4="; + deepClone = false; + }) + + (pkgs.fetchgit { + name = "fribidi"; + url = "https://github.com/fribidi/fribidi.git"; + rev = "v1.0.16"; + hash = "sha256-VXDgyqpgjeNHnKfihWW0Oe1yzwIv1Bw8mrs8wLBJZgw="; + deepClone = false; + }) + + (pkgs.fetchurl { + name = "harfbuzz.tar.xz"; + url = "https://github.com/harfbuzz/harfbuzz/releases/download/12.1.0/harfbuzz-12.1.0.tar.xz"; + hash = "sha256-5cgbf24LEC37AAz6QkU4uOiWq3ii9Lil7IyuYqtDNp4="; + }) + + (pkgs.fetchgit { + name = "libass"; + url = "https://github.com/libass/libass"; + rev = "0.17.4"; + hash = "sha256-GgeJ9W1x5IOSyIcbR8F4D5BmAqrkG6xXs85wlVXVsig="; + deepClone = false; + }) + + (pkgs.fetchgit { + name = "libfontconfig"; + url = "https://gitlab.freedesktop.org/fontconfig/fontconfig.git"; + rev = "2.17.1"; + hash = "sha256-RCYZctF3Nheopx8RojjlyE8Z2w7C7Nfyi2aobVD9o5Q="; + deepClone = false; + }) + + (pkgs.fetchgit { + name = "libdisplay-info"; + url = "https://gitlab.freedesktop.org/emersion/libdisplay-info.git"; + rev = "0.3.0"; + hash = "sha256-nXf2KGovNKvcchlHlzKBkAOeySMJXgxMpbi5z9gLrdc="; + deepClone = false; + }) + + (pkgs.fetchurl { + name = "libjpeg-turbo.tar.gz"; + url = "https://github.com/libjpeg-turbo/libjpeg-turbo/releases/download/3.1.2/libjpeg-turbo-3.1.2.tar.gz"; + hash = "sha256-jwASI0tGTOUIkMSQ8YGU+ROnsfTmoD1mRBefoPhn0M8="; + }) + + (pkgs.fetchgit { + name = "libplacebo"; + url = "https://github.com/haasn/libplacebo"; + rev = "v7.360.0"; + hash = "sha256-KGPjP2Aia033XLopnlftkhESEXjmOG2j5mHLgpG5dCY="; + deepClone = false; + }) + + (pkgs.fetchurl { + name = "libpng.tar.gz"; + url = "https://github.com/pnggroup/libpng/archive/v1.6.50.tar.gz"; + hash = "sha256-cRWOU8/fKHe8mbyrM2QdeN8/SObg2q0DCv6cuMAxqkY="; + }) + (pkgs.fetchurl { + name = "libpng-patch.zip"; + url = "https://wrapdb.mesonbuild.com/v2/libpng_1.6.50-2/get_patch"; + hash = "sha256-qck2K7jLtCLIZIB6F0l33sOGjN/4yjgOJ7q27hyqIuo="; + }) + + (pkgs.fetchgit { + name = "mpv"; + url = "https://github.com/mpv-player/mpv.git"; + rev = "v0.41.0"; + hash = "sha256-gJWqfvPE6xOKlgj2MzZgXiyOKxksJlY/tL6T/BeG19c="; + deepClone = false; + }) + + (pkgs.fetchgit { + name = "libuchardet"; + url = "https://gitlab.freedesktop.org/uchardet/uchardet.git"; + rev = "v0.0.8"; + hash = "sha256-5HSwFaclje5JkzOZKILgy2BGxLyFeDq/9p24KiTlTzE="; + deepClone = false; + }) + + (pkgs.fetchurl { + name = "openal-soft.tar.gz"; + url = "https://github.com/kcat/openal-soft/archive/refs/tags/1.24.3.tar.gz"; + hash = "sha256-fh/s3rRef3hyK3dsXPML0zk0uWHX/SoR4ElOBkzGMc4="; + }) + (pkgs.fetchurl { + name = "openal-soft-patch.zip"; + url = "https://wrapdb.mesonbuild.com/v2/openal-soft_1.24.3-1/get_patch"; + hash = "sha256-mmwWYizH3bCKga+qnESSXlR1GMlIXymmjRUC4OvlTRY="; + }) + + (pkgs.fetchurl { + name = "zlib.tar.gz"; + url = "http://zlib.net/fossils/zlib-1.3.1.tar.gz"; + hash = "sha256-mpOyt9/ax3zrpaVYpYDnRmfdb+3kWFuR7vtg8Dty3yM="; + }) + (pkgs.fetchurl { + name = "zlib-patch.zip"; + url = "https://wrapdb.mesonbuild.com/v2/zlib_1.3.1-2/get_patch"; + hash = "sha256-nKzqAuERmWS8Uekt0jWbFN9yOjbP4N8ceNVdnJ8nY64="; + }) + + (pkgs.fetchurl { + name = "icu.tar.gz"; + url = "https://github.com/unicode-org/icu/releases/download/release-77-1/icu4c-77_1-src.tgz"; + hash = "sha256-WI5DH3cyfDkDH/u4hDwOO8EiwhE3RIX6h9xfP6/yQGE="; + }) + (pkgs.fetchurl { + name = "icu-patch.zip"; + url = "https://wrapdb.mesonbuild.com/v2/icu_77.1-3/get_patch"; + hash = "sha256-3yOnIOzkzYx271C8r+DYAMjBEReAa/IH1llzqV8Nzqc="; + }) + + (pkgs.fetchurl { + name = "libxml2.tar.xz"; + url = "https://download.gnome.org/sources/libxml2/2.12/libxml2-2.12.6.tar.xz"; + hash = "sha256-iJxZOogaPbX92WzJMYyH3zTrZI7fxFgnKtRv1gc1P7s="; + }) + (pkgs.fetchurl { + name = "libxml2-patch.zip"; + url = "https://wrapdb.mesonbuild.com/v2/libxml2_2.12.6-1/get_patch"; + hash = "sha256-d3vn/9xBZ+TQs/QF4TSSZky97y5s9vaojMVeyAESYUw="; + }) + + (pkgs.fetchgit { + name = "gperf"; + url = "https://gitlab.freedesktop.org/tpm/gperf.git"; + rev = "c24359b4eab86d71c655c3b3fc969f13aac879ce"; + hash = "sha256-IbWwGTSFRTnPxGJHaQIA0ywvZOCec6gpdrobXtZ6TO4="; + deepClone = false; + }) + + (pkgs.fetchurl { + name = "brotli.tar.gz"; + url = "https://github.com/google/brotli/archive/v1.1.0.tar.gz"; + hash = "sha256-5yCmyilCi4A/StFlNxdx9TmPq6OX7fZ3iDehhZnqE/8="; + }) + (pkgs.fetchurl { + name = "brotli-patch.zip"; + url = "https://wrapdb.mesonbuild.com/v2/google-brotli_1.1.0-3/get_patch"; + hash = "sha256-d+daO7AZpLZTGVoVrxfMZcVvuMztJRkVT85Oa7Q8dOI="; + }) +] diff --git a/native/nix/mpv-config.nix b/native/nix/mpv-config.nix new file mode 100644 index 0000000..e0855b9 --- /dev/null +++ b/native/nix/mpv-config.nix @@ -0,0 +1,20 @@ +{ pkgs +}: +let + mpv = { arch, hash }: rec { + source_url = "https://repoflow.silenium.dev/api/universal/personal/github-releases/shinchiro/mpv-winbuild-cmake/20260225/mpv-dev-${arch}-20260225-git-92ed2d2.7z"; + source_hash = hash; + source_filename = pkgs.lib.lists.last (pkgs.lib.strings.split "/" source_url); + directory = builtins.elemAt (pkgs.lib.strings.split "." source_filename) 0; + }; +in +{ + "x86_64-windows" = mpv { + arch = "x86_64"; + hash = "sha256-XSZqaJm4uxdaaFfJPFNnnwQJUxZ6K+1luD7bA+C0i2U="; + }; + "aarch64-windows" = { + arch = "aarch64"; + hash = "sha256-eDtgyMqU7adllvKzVfhg813/5vVqouN2Ly/H/T9zBNI="; + }; +} diff --git a/native/src/cpp/util/Errors.cpp b/native/src/cpp/util/Errors.cpp deleted file mode 100644 index 3627694..0000000 --- a/native/src/cpp/util/Errors.cpp +++ /dev/null @@ -1,42 +0,0 @@ -// -// Created by silenium-dev on 7/21/24. -// - -#include -#include -#include - -extern "C" { -JNIEXPORT jstring JNICALL Java_dev_silenium_multimedia_core_util_ErrorsKt_mpvErrorStringN( - JNIEnv *env, - jobject thiz, - const jint error) { - return env->NewStringUTF(mpv_error_string(error)); -} - -JNIEXPORT jstring JNICALL Java_dev_silenium_multimedia_core_util_ErrorsKt_glErrorStringN( - JNIEnv *env, - jobject thiz, - const jint error) { - switch (error) { - case GL_NO_ERROR: - return env->NewStringUTF("GL_NO_ERROR"); - case GL_INVALID_ENUM: - return env->NewStringUTF("GL_INVALID_ENUM"); - case GL_INVALID_VALUE: - return env->NewStringUTF("GL_INVALID_VALUE"); - case GL_INVALID_OPERATION: - return env->NewStringUTF("GL_INVALID_OPERATION"); - case GL_INVALID_FRAMEBUFFER_OPERATION: - return env->NewStringUTF("GL_INVALID_FRAMEBUFFER_OPERATION"); - case GL_OUT_OF_MEMORY: - return env->NewStringUTF("GL_OUT_OF_MEMORY"); - case GL_STACK_UNDERFLOW: - return env->NewStringUTF("GL_STACK_UNDERFLOW"); - case GL_STACK_OVERFLOW: - return env->NewStringUTF("GL_STACK_OVERFLOW"); - default: - return env->NewStringUTF("Unknown"); - } -} -} diff --git a/native/src/cpp/helper/errors.cpp b/native/src/helper/errors.cpp similarity index 91% rename from native/src/cpp/helper/errors.cpp rename to native/src/helper/errors.cpp index 3774fdf..c23ba83 100644 --- a/native/src/cpp/helper/errors.cpp +++ b/native/src/helper/errors.cpp @@ -4,29 +4,28 @@ #include "errors.hpp" -#include #include #include -jobject boxedLong(JNIEnv *env, const long value) { +jobject boxedLong(JNIEnv *env, const jlong value) { const auto boxedClass = env->FindClass("java/lang/Long"); const auto constructor = env->GetMethodID(boxedClass, "", "(J)V"); return env->NewObject(boxedClass, constructor, value); } -jobject boxedDouble(JNIEnv *env, const double value) { +jobject boxedDouble(JNIEnv *env, const jdouble value) { const auto boxedClass = env->FindClass("java/lang/Double"); const auto constructor = env->GetMethodID(boxedClass, "", "(D)V"); return env->NewObject(boxedClass, constructor, value); } -jobject boxedBool(JNIEnv *env, const bool value) { +jobject boxedBool(JNIEnv *env, const jboolean value) { const auto boxedClass = env->FindClass("java/lang/Boolean"); const auto constructor = env->GetMethodID(boxedClass, "", "(Z)V"); return env->NewObject(boxedClass, constructor, value); } -jobject boxedInt(JNIEnv *env, const int value) { +jobject boxedInt(JNIEnv *env, const jint value) { const auto boxedClass = env->FindClass("java/lang/Integer"); const auto constructor = env->GetMethodID(boxedClass, "", "(I)V"); return env->NewObject(boxedClass, constructor, value); @@ -38,13 +37,13 @@ jobject pair(JNIEnv *env, jobject first, jobject second) { return env->NewObject(pairClass, constructor, first, second); } -jobject resultSuccess(JNIEnv *env, const long value, const long secondValue) { +jobject resultSuccess(JNIEnv *env, const jlong value, const jlong secondValue) { const auto boxed = boxedLong(env, value); const auto boxed2 = boxedLong(env, secondValue); return pair(env, boxed, boxed2); } -jobject resultSuccess(JNIEnv *env, const long value) { +jobject resultSuccess(JNIEnv *env, const jlong value) { const auto boxed = boxedLong(env, value); return boxed; } @@ -53,12 +52,12 @@ jobject resultSuccess(JNIEnv *env, const char *value) { return env->NewStringUTF(value); } -jobject resultSuccess(JNIEnv *env, const double value) { +jobject resultSuccess(JNIEnv *env, const jdouble value) { const auto boxed = boxedDouble(env, value); return boxed; } -jobject resultSuccess(JNIEnv *env, const bool value) { +jobject resultSuccess(JNIEnv *env, const jboolean value) { const auto boxed = boxedBool(env, value); return boxed; } @@ -106,6 +105,7 @@ jobject glResultFailure(JNIEnv *env, const char *operation, const GLenum returnC const auto errorResult = env->NewObject(resultClass, resultConstructor, error); return errorResult; } + jobject vaResultFailure(JNIEnv *env, const char *operation, const int returnCode) { const auto resultClass = env->FindClass("kotlin/Result$Failure"); const auto errorClass = env->FindClass("dev/silenium/multimedia/core/util/VAException"); diff --git a/native/src/cpp/helper/errors.hpp b/native/src/helper/errors.hpp similarity index 52% rename from native/src/cpp/helper/errors.hpp rename to native/src/helper/errors.hpp index 397aab5..5a7c3e7 100644 --- a/native/src/cpp/helper/errors.hpp +++ b/native/src/helper/errors.hpp @@ -7,24 +7,30 @@ #include #include +#include std::string avErrorString(int error); -jobject boxedLong(JNIEnv *env, long value); +jobject boxedLong(JNIEnv *env, jlong value); +jobject boxedDouble(JNIEnv *env, jdouble value); -jobject boxedInt(JNIEnv *env, int value); +jobject boxedBool(JNIEnv *env, jboolean value); +jobject boxedInt(JNIEnv *env, jint value); jobject pair(JNIEnv *env, jobject first, jobject second); -jobject resultSuccess(JNIEnv *env, long value, long secondValue); -jobject resultSuccess(JNIEnv *env, long value); +jobject resultSuccess(JNIEnv *env, jlong value, jlong secondValue); +jobject resultSuccess(JNIEnv *env, jlong value); jobject resultSuccess(JNIEnv *env, const char *value); -jobject resultSuccess(JNIEnv *env, double value); -jobject resultSuccess(JNIEnv *env, bool value); +jobject resultSuccess(JNIEnv *env, jdouble value); +jobject resultSuccess(JNIEnv *env, jboolean value); jobject resultSuccess(JNIEnv *env); jobject resultSuccessNull(); +#ifdef TARGET_LINUX jobject eglResultFailure(JNIEnv *env, const char *operation, long returnCode); -jobject glResultFailure(JNIEnv *env, const char *operation, uint returnCode); +#endif + +jobject glResultFailure(JNIEnv *env, const char *operation, GLenum returnCode); jobject vaResultFailure(JNIEnv *env, const char *operation, int returnCode); diff --git a/native/src/meson.build b/native/src/meson.build new file mode 100644 index 0000000..2f8be65 --- /dev/null +++ b/native/src/meson.build @@ -0,0 +1,36 @@ +mpv_dep = mpv.get_variable('libmpv_dep') +jni = dependency('jni') + +deps = [jni, mpv_dep] + +sources = [ + 'compose-av', + 'util/Errors.cpp', + 'helper/errors.hpp', + 'helper/errors.cpp', + 'mpv/MPV.cpp', + 'mpv/mpv_platform.hpp', +] + +os = host_machine.system() +if os == 'linux' + egl = dependency('egl') + glx = dependency('glx') + x11 = dependency('x11') + va = dependency('libva') + deps += [egl, glx, x11, va] + sources += ['mpv/mpv_linux.cpp'] + add_project_arguments('-DTARGET_LINUX', language: 'cpp') +elif os == 'windows' + sources += ['mpv/mpv_windows.cpp'] + cc = meson.get_compiler('cpp') + gl = cc.find_library('opengl32') + deps += [gl] + add_project_arguments('-DTARGET_WINDOWS', language: 'cpp') +endif + +shared_library( + sources, + dependencies : deps, + cpp_args : ['-Wimplicit-fallthrough'], +) diff --git a/native/src/cpp/mpv/MPV.cpp b/native/src/mpv/MPV.cpp similarity index 69% rename from native/src/cpp/mpv/MPV.cpp rename to native/src/mpv/MPV.cpp index 547eeb6..2d36b76 100644 --- a/native/src/cpp/mpv/MPV.cpp +++ b/native/src/mpv/MPV.cpp @@ -3,21 +3,20 @@ // #include "helper/errors.hpp" +#include "mpv_platform.hpp" - -#include #include -#include -#include #include #include #include #include #include #include +#include +#include +#include #include -#include #include #include @@ -28,7 +27,7 @@ auto fnptr_(Callable &&c, Ret (*)(Args...)) { if (used) { using type = decltype(storage); storage.~type(); - new (&storage) type(std::forward(c)); + new(&storage) type(std::forward(c)); } used = true; @@ -49,6 +48,12 @@ struct MpvContext { mpv_handle *handle; jobject object; jmethodID method; + + std::thread eventLoop; + std::mutex mtx; + std::condition_variable cv; + std::atomic wakeup{false}; + std::atomic running{true}; }; jobject eventDataToJava(JNIEnv *env, mpv_event_property *prop) { @@ -126,6 +131,7 @@ void eventCallback(JNIEnv *env, const mpv_event *event, jobject callback) { return; } env->CallVoidMethod(callback, method, name, value); + break; } case MPV_EVENT_GET_PROPERTY_REPLY: { const auto prop = static_cast(event->data); @@ -152,6 +158,7 @@ void eventCallback(JNIEnv *env, const mpv_event *event, jobject callback) { return; } env->CallVoidMethod(callback, method, static_cast(event->reply_userdata), value); + break; } case MPV_EVENT_SET_PROPERTY_REPLY: { jobject value = nullptr; @@ -172,6 +179,7 @@ void eventCallback(JNIEnv *env, const mpv_event *event, jobject callback) { return; } env->CallVoidMethod(callback, method, static_cast(event->reply_userdata), value); + break; } case MPV_EVENT_COMMAND_REPLY: { // const auto reply = static_cast(event->data); @@ -193,26 +201,60 @@ void eventCallback(JNIEnv *env, const mpv_event *event, jobject callback) { return; } env->CallVoidMethod(callback, method, static_cast(event->reply_userdata), value); + break; } default: break; } } -void handle_mpv_events(void *ctx) { +// void handle_mpv_events(void *ctx) { +// const auto context = static_cast(ctx); +// JNIEnv *env; +// const auto res = context->jvm->AttachCurrentThread(reinterpret_cast(&env), nullptr); +// if (res != JNI_OK) { +// std::cerr << "Failed to attach current thread" << std::endl; +// return; +// } +// while (true) { +// const auto event = mpv_wait_event(context->handle, 0); +// if (event->event_id == MPV_EVENT_NONE) { +// break; +// } +// eventCallback(env, event, context->object); +// } +// context->jvm->DetachCurrentThread(); +// } + +void handle_mpv_wakeup(void *ctx) { const auto context = static_cast(ctx); + { + std::lock_guard lock(context->mtx); + context->wakeup = true; + } + context->cv.notify_one(); +} + +void mpv_event_loop(MpvContext *context) { JNIEnv *env; const auto res = context->jvm->AttachCurrentThread(reinterpret_cast(&env), nullptr); if (res != JNI_OK) { std::cerr << "Failed to attach current thread" << std::endl; return; } - while (true) { - const auto event = mpv_wait_event(context->handle, 0); - if (event->event_id == MPV_EVENT_NONE) { - break; + while (context->running) { + std::unique_lock lock(context->mtx); + context->cv.wait(lock, [&] { return context->wakeup.load() || !context->running.load(); }); + context->wakeup = false; + lock.unlock(); + + if (!context->running.load()) break; + + while (context->running) { + const auto event = mpv_wait_event(context->handle, 0); + if (event->event_id == MPV_EVENT_NONE) break; + eventCallback(env, event, context->object); } - eventCallback(env, event, context->object); } context->jvm->DetachCurrentThread(); } @@ -220,18 +262,21 @@ void handle_mpv_events(void *ctx) { extern "C" { // Client JNIEXPORT jobject JNICALL Java_dev_silenium_multimedia_core_mpv_MPVKt_createN(JNIEnv *env, jobject thiz) { + setlocale(LC_NUMERIC, "C"); const auto handle = mpv_create(); if (handle == nullptr) { return mpvResultFailure(env, "mpv_create", MPV_ERROR_NOMEM); } - return resultSuccess(env, reinterpret_cast(handle)); + return resultSuccess(env, reinterpret_cast(handle)); } -JNIEXPORT void JNICALL Java_dev_silenium_multimedia_core_mpv_MPVKt_destroyN(JNIEnv *env, jobject thiz, const jlong handle) { +JNIEXPORT void JNICALL Java_dev_silenium_multimedia_core_mpv_MPVKt_destroyN( + JNIEnv *env, jobject thiz, const jlong handle) { mpv_terminate_destroy(reinterpret_cast(handle)); } -JNIEXPORT jobject JNICALL Java_dev_silenium_multimedia_core_mpv_MPVKt_setOptionStringN(JNIEnv *env, jobject thiz, const jlong handle, jstring name, jstring value) { +JNIEXPORT jobject JNICALL Java_dev_silenium_multimedia_core_mpv_MPVKt_setOptionStringN( + JNIEnv *env, jobject thiz, const jlong handle, jstring name, jstring value) { const char *nameStr = env->GetStringUTFChars(name, nullptr); const char *valueStr = env->GetStringUTFChars(value, nullptr); const auto ret = mpv_set_option_string(reinterpret_cast(handle), nameStr, valueStr); @@ -243,7 +288,8 @@ JNIEXPORT jobject JNICALL Java_dev_silenium_multimedia_core_mpv_MPVKt_setOptionS return resultSuccess(env); } -JNIEXPORT jobject JNICALL Java_dev_silenium_multimedia_core_mpv_MPVKt_setCallbackN(JNIEnv *env, jobject thiz, const jlong handle_, jobject callback) { +JNIEXPORT jobject JNICALL Java_dev_silenium_multimedia_core_mpv_MPVKt_setCallbackN( + JNIEnv *env, jobject thiz, const jlong handle_, jobject callback) { const auto handle = reinterpret_cast(handle_); JavaVM *jvm; if (const auto ret = env->GetJavaVM(&jvm); ret != JNI_OK) { @@ -251,18 +297,27 @@ JNIEXPORT jobject JNICALL Java_dev_silenium_multimedia_core_mpv_MPVKt_setCallbac return mpvResultFailure(env, "GetJavaVM", MPV_ERROR_GENERIC); } const auto ctx = new MpvContext{jvm, handle, env->NewGlobalRef(callback)}; - mpv_set_wakeup_callback(handle, handle_mpv_events, ctx); - return resultSuccess(env, reinterpret_cast(ctx)); + mpv_set_wakeup_callback(handle, handle_mpv_wakeup, ctx); + // mpv_set_wakeup_callback(handle, handle_mpv_events, ctx); + + std::thread eventLoopThread{mpv_event_loop, ctx}; + ctx->eventLoop = std::move(eventLoopThread); + return resultSuccess(env, reinterpret_cast(ctx)); } -JNIEXPORT void JNICALL Java_dev_silenium_multimedia_core_mpv_MPVKt_unsetCallbackN(JNIEnv *env, jobject thiz, const jlong ctx) { +JNIEXPORT void JNICALL Java_dev_silenium_multimedia_core_mpv_MPVKt_unsetCallbackN( + JNIEnv *env, jobject thiz, const jlong ctx) { const auto context = reinterpret_cast(ctx); + context->running = false; + context->cv.notify_all(); + context->eventLoop.join(); mpv_set_wakeup_callback(context->handle, nullptr, nullptr); env->DeleteGlobalRef(context->object); delete context; } -JNIEXPORT jobject JNICALL Java_dev_silenium_multimedia_core_mpv_MPVKt_commandAsyncN(JNIEnv *env, jobject thiz, const jlong handle, jobjectArray args, const jlong replyUserdata) { +JNIEXPORT jobject JNICALL Java_dev_silenium_multimedia_core_mpv_MPVKt_commandAsyncN( + JNIEnv *env, jobject thiz, const jlong handle, jobjectArray args, const jlong replyUserdata) { const auto size = env->GetArrayLength(args); std::vector argv(size + 1); for (auto i = 0; i < size; i++) { @@ -282,7 +337,8 @@ JNIEXPORT jobject JNICALL Java_dev_silenium_multimedia_core_mpv_MPVKt_commandAsy return resultSuccess(env); } -JNIEXPORT jobject JNICALL Java_dev_silenium_multimedia_core_mpv_MPVKt_commandN(JNIEnv *env, jobject thiz, const jlong handle, jobjectArray args) { +JNIEXPORT jobject JNICALL Java_dev_silenium_multimedia_core_mpv_MPVKt_commandN( + JNIEnv *env, jobject thiz, const jlong handle, jobjectArray args) { const auto size = env->GetArrayLength(args); std::vector argv(size + 1); for (auto i = 0; i < size; i++) { @@ -305,7 +361,8 @@ JNIEXPORT jobject JNICALL Java_dev_silenium_multimedia_core_mpv_MPVKt_commandN(J return resultSuccess(env); } -JNIEXPORT jobject JNICALL Java_dev_silenium_multimedia_core_mpv_MPVKt_commandStringN(JNIEnv *env, jobject thiz, const jlong handle, jstring command) { +JNIEXPORT jobject JNICALL Java_dev_silenium_multimedia_core_mpv_MPVKt_commandStringN( + JNIEnv *env, jobject thiz, const jlong handle, jstring command) { const char *commandStr = env->GetStringUTFChars(command, nullptr); const auto ret = mpv_command_string(reinterpret_cast(handle), commandStr); env->ReleaseStringUTFChars(command, commandStr); @@ -315,9 +372,11 @@ JNIEXPORT jobject JNICALL Java_dev_silenium_multimedia_core_mpv_MPVKt_commandStr return resultSuccess(env); } -JNIEXPORT jobject JNICALL Java_dev_silenium_multimedia_core_mpv_MPVKt_getPropertyStringAsyncN(JNIEnv *env, jobject thiz, const jlong handle, jstring name, const jlong replyUserdata) { +JNIEXPORT jobject JNICALL Java_dev_silenium_multimedia_core_mpv_MPVKt_getPropertyStringAsyncN( + JNIEnv *env, jobject thiz, const jlong handle, jstring name, const jlong replyUserdata) { const char *nameStr = env->GetStringUTFChars(name, nullptr); - const auto ret = mpv_get_property_async(reinterpret_cast(handle), replyUserdata, nameStr, MPV_FORMAT_STRING); + const auto ret = mpv_get_property_async(reinterpret_cast(handle), replyUserdata, nameStr, + MPV_FORMAT_STRING); env->ReleaseStringUTFChars(name, nameStr); if (ret < 0) { return mpvResultFailure(env, "mpv_get_property_async string", ret); @@ -325,9 +384,11 @@ JNIEXPORT jobject JNICALL Java_dev_silenium_multimedia_core_mpv_MPVKt_getPropert return resultSuccess(env); } -JNIEXPORT jobject JNICALL Java_dev_silenium_multimedia_core_mpv_MPVKt_getPropertyLongAsyncN(JNIEnv *env, jobject thiz, const jlong handle, jstring name, const jlong replyUserdata) { +JNIEXPORT jobject JNICALL Java_dev_silenium_multimedia_core_mpv_MPVKt_getPropertyLongAsyncN( + JNIEnv *env, jobject thiz, const jlong handle, jstring name, const jlong replyUserdata) { const char *nameStr = env->GetStringUTFChars(name, nullptr); - const auto ret = mpv_get_property_async(reinterpret_cast(handle), replyUserdata, nameStr, MPV_FORMAT_INT64); + const auto ret = mpv_get_property_async(reinterpret_cast(handle), replyUserdata, nameStr, + MPV_FORMAT_INT64); env->ReleaseStringUTFChars(name, nameStr); if (ret < 0) { return mpvResultFailure(env, "mpv_get_property_async int64", ret); @@ -335,9 +396,11 @@ JNIEXPORT jobject JNICALL Java_dev_silenium_multimedia_core_mpv_MPVKt_getPropert return resultSuccess(env); } -JNIEXPORT jobject JNICALL Java_dev_silenium_multimedia_core_mpv_MPVKt_getPropertyDoubleAsyncN(JNIEnv *env, jobject thiz, const jlong handle, jstring name, const jlong replyUserdata) { +JNIEXPORT jobject JNICALL Java_dev_silenium_multimedia_core_mpv_MPVKt_getPropertyDoubleAsyncN( + JNIEnv *env, jobject thiz, const jlong handle, jstring name, const jlong replyUserdata) { const char *nameStr = env->GetStringUTFChars(name, nullptr); - const auto ret = mpv_get_property_async(reinterpret_cast(handle), replyUserdata, nameStr, MPV_FORMAT_DOUBLE); + const auto ret = mpv_get_property_async(reinterpret_cast(handle), replyUserdata, nameStr, + MPV_FORMAT_DOUBLE); env->ReleaseStringUTFChars(name, nameStr); if (ret < 0) { return mpvResultFailure(env, "mpv_get_property_async double", ret); @@ -345,9 +408,11 @@ JNIEXPORT jobject JNICALL Java_dev_silenium_multimedia_core_mpv_MPVKt_getPropert return resultSuccess(env); } -JNIEXPORT jobject JNICALL Java_dev_silenium_multimedia_core_mpv_MPVKt_getPropertyFlagAsyncN(JNIEnv *env, jobject thiz, const jlong handle, jstring name, const jlong replyUserdata) { +JNIEXPORT jobject JNICALL Java_dev_silenium_multimedia_core_mpv_MPVKt_getPropertyFlagAsyncN( + JNIEnv *env, jobject thiz, const jlong handle, jstring name, const jlong replyUserdata) { const char *nameStr = env->GetStringUTFChars(name, nullptr); - const auto ret = mpv_get_property_async(reinterpret_cast(handle), replyUserdata, nameStr, MPV_FORMAT_FLAG); + const auto ret = mpv_get_property_async(reinterpret_cast(handle), replyUserdata, nameStr, + MPV_FORMAT_FLAG); env->ReleaseStringUTFChars(name, nameStr); if (ret < 0) { return mpvResultFailure(env, "mpv_get_property_async flag", ret); @@ -356,7 +421,8 @@ JNIEXPORT jobject JNICALL Java_dev_silenium_multimedia_core_mpv_MPVKt_getPropert } -JNIEXPORT jobject JNICALL Java_dev_silenium_multimedia_core_mpv_MPVKt_getPropertyStringN(JNIEnv *env, jobject thiz, const jlong handle, jstring name) { +JNIEXPORT jobject JNICALL Java_dev_silenium_multimedia_core_mpv_MPVKt_getPropertyStringN( + JNIEnv *env, jobject thiz, const jlong handle, jstring name) { const char *nameStr = env->GetStringUTFChars(name, nullptr); char *value; const auto ret = mpv_get_property(reinterpret_cast(handle), nameStr, MPV_FORMAT_STRING, &value); @@ -372,9 +438,10 @@ JNIEXPORT jobject JNICALL Java_dev_silenium_multimedia_core_mpv_MPVKt_getPropert return result; } -JNIEXPORT jobject JNICALL Java_dev_silenium_multimedia_core_mpv_MPVKt_getPropertyLongN(JNIEnv *env, jobject thiz, const jlong handle, jstring name) { +JNIEXPORT jobject JNICALL Java_dev_silenium_multimedia_core_mpv_MPVKt_getPropertyLongN( + JNIEnv *env, jobject thiz, const jlong handle, jstring name) { const char *nameStr = env->GetStringUTFChars(name, nullptr); - long value; + jlong value; const auto ret = mpv_get_property(reinterpret_cast(handle), nameStr, MPV_FORMAT_INT64, &value); env->ReleaseStringUTFChars(name, nameStr); if (ret == MPV_ERROR_PROPERTY_UNAVAILABLE) { @@ -386,7 +453,8 @@ JNIEXPORT jobject JNICALL Java_dev_silenium_multimedia_core_mpv_MPVKt_getPropert return resultSuccess(env, value); } -JNIEXPORT jobject JNICALL Java_dev_silenium_multimedia_core_mpv_MPVKt_getPropertyDoubleN(JNIEnv *env, jobject thiz, const jlong handle, jstring name) { +JNIEXPORT jobject JNICALL Java_dev_silenium_multimedia_core_mpv_MPVKt_getPropertyDoubleN( + JNIEnv *env, jobject thiz, const jlong handle, jstring name) { const char *nameStr = env->GetStringUTFChars(name, nullptr); double value; const auto ret = mpv_get_property(reinterpret_cast(handle), nameStr, MPV_FORMAT_DOUBLE, &value); @@ -400,9 +468,10 @@ JNIEXPORT jobject JNICALL Java_dev_silenium_multimedia_core_mpv_MPVKt_getPropert return resultSuccess(env, value); } -JNIEXPORT jobject JNICALL Java_dev_silenium_multimedia_core_mpv_MPVKt_getPropertyFlagN(JNIEnv *env, jobject thiz, const jlong handle, jstring name) { +JNIEXPORT jobject JNICALL Java_dev_silenium_multimedia_core_mpv_MPVKt_getPropertyFlagN( + JNIEnv *env, jobject thiz, const jlong handle, jstring name) { const char *nameStr = env->GetStringUTFChars(name, nullptr); - bool value; + jboolean value; const auto ret = mpv_get_property(reinterpret_cast(handle), nameStr, MPV_FORMAT_FLAG, &value); env->ReleaseStringUTFChars(name, nameStr); if (ret == MPV_ERROR_PROPERTY_UNAVAILABLE) { @@ -414,10 +483,12 @@ JNIEXPORT jobject JNICALL Java_dev_silenium_multimedia_core_mpv_MPVKt_getPropert return resultSuccess(env, value); } -JNIEXPORT jobject JNICALL Java_dev_silenium_multimedia_core_mpv_MPVKt_setPropertyStringAsyncN(JNIEnv *env, jobject thiz, const jlong handle, jstring name, jstring value, const jlong replyUserdata) { +JNIEXPORT jobject JNICALL Java_dev_silenium_multimedia_core_mpv_MPVKt_setPropertyStringAsyncN( + JNIEnv *env, jobject thiz, const jlong handle, jstring name, jstring value, const jlong replyUserdata) { const char *nameStr = env->GetStringUTFChars(name, nullptr); auto valueStr = const_cast(env->GetStringUTFChars(value, nullptr)); - const auto ret = mpv_set_property_async(reinterpret_cast(handle), replyUserdata, nameStr, MPV_FORMAT_STRING, &valueStr); + const auto ret = mpv_set_property_async(reinterpret_cast(handle), replyUserdata, nameStr, + MPV_FORMAT_STRING, &valueStr); env->ReleaseStringUTFChars(name, nameStr); env->ReleaseStringUTFChars(value, valueStr); if (ret < 0) { @@ -426,9 +497,11 @@ JNIEXPORT jobject JNICALL Java_dev_silenium_multimedia_core_mpv_MPVKt_setPropert return resultSuccess(env); } -JNIEXPORT jobject JNICALL Java_dev_silenium_multimedia_core_mpv_MPVKt_setPropertyLongAsyncN(JNIEnv *env, jobject thiz, const jlong handle, jstring name, jlong value, const jlong replyUserdata) { +JNIEXPORT jobject JNICALL Java_dev_silenium_multimedia_core_mpv_MPVKt_setPropertyLongAsyncN( + JNIEnv *env, jobject thiz, const jlong handle, jstring name, jlong value, const jlong replyUserdata) { const char *nameStr = env->GetStringUTFChars(name, nullptr); - const auto ret = mpv_set_property_async(reinterpret_cast(handle), replyUserdata, nameStr, MPV_FORMAT_INT64, &value); + const auto ret = mpv_set_property_async(reinterpret_cast(handle), replyUserdata, nameStr, + MPV_FORMAT_INT64, &value); env->ReleaseStringUTFChars(name, nameStr); if (ret < 0) { return mpvResultFailure(env, "mpv_set_property_async int64", ret); @@ -436,9 +509,11 @@ JNIEXPORT jobject JNICALL Java_dev_silenium_multimedia_core_mpv_MPVKt_setPropert return resultSuccess(env); } -JNIEXPORT jobject JNICALL Java_dev_silenium_multimedia_core_mpv_MPVKt_setPropertyDoubleAsyncN(JNIEnv *env, jobject thiz, const jlong handle, jstring name, jdouble value, const jlong replyUserdata) { +JNIEXPORT jobject JNICALL Java_dev_silenium_multimedia_core_mpv_MPVKt_setPropertyDoubleAsyncN( + JNIEnv *env, jobject thiz, const jlong handle, jstring name, jdouble value, const jlong replyUserdata) { const char *nameStr = env->GetStringUTFChars(name, nullptr); - const auto ret = mpv_set_property_async(reinterpret_cast(handle), replyUserdata, nameStr, MPV_FORMAT_DOUBLE, &value); + const auto ret = mpv_set_property_async(reinterpret_cast(handle), replyUserdata, nameStr, + MPV_FORMAT_DOUBLE, &value); env->ReleaseStringUTFChars(name, nameStr); if (ret < 0) { return mpvResultFailure(env, "mpv_set_property_async double", ret); @@ -446,9 +521,11 @@ JNIEXPORT jobject JNICALL Java_dev_silenium_multimedia_core_mpv_MPVKt_setPropert return resultSuccess(env); } -JNIEXPORT jobject JNICALL Java_dev_silenium_multimedia_core_mpv_MPVKt_setPropertyFlagAsyncN(JNIEnv *env, jobject thiz, const jlong handle, jstring name, jboolean value, const jlong replyUserdata) { +JNIEXPORT jobject JNICALL Java_dev_silenium_multimedia_core_mpv_MPVKt_setPropertyFlagAsyncN( + JNIEnv *env, jobject thiz, const jlong handle, jstring name, jboolean value, const jlong replyUserdata) { const char *nameStr = env->GetStringUTFChars(name, nullptr); - const auto ret = mpv_set_property_async(reinterpret_cast(handle), replyUserdata, nameStr, MPV_FORMAT_FLAG, &value); + const auto ret = mpv_set_property_async(reinterpret_cast(handle), replyUserdata, nameStr, + MPV_FORMAT_FLAG, &value); env->ReleaseStringUTFChars(name, nameStr); if (ret < 0) { return mpvResultFailure(env, "mpv_set_property_async flag", ret); @@ -456,7 +533,8 @@ JNIEXPORT jobject JNICALL Java_dev_silenium_multimedia_core_mpv_MPVKt_setPropert return resultSuccess(env); } -JNIEXPORT jobject JNICALL Java_dev_silenium_multimedia_core_mpv_MPVKt_setPropertyStringN(JNIEnv *env, jobject thiz, const jlong handle, jstring name, jstring value) { +JNIEXPORT jobject JNICALL Java_dev_silenium_multimedia_core_mpv_MPVKt_setPropertyStringN( + JNIEnv *env, jobject thiz, const jlong handle, jstring name, jstring value) { const char *nameStr = env->GetStringUTFChars(name, nullptr); const char *valueStr = env->GetStringUTFChars(value, nullptr); const auto ret = mpv_set_property_string(reinterpret_cast(handle), nameStr, valueStr); @@ -468,7 +546,8 @@ JNIEXPORT jobject JNICALL Java_dev_silenium_multimedia_core_mpv_MPVKt_setPropert return resultSuccess(env); } -JNIEXPORT jobject JNICALL Java_dev_silenium_multimedia_core_mpv_MPVKt_setPropertyLongN(JNIEnv *env, jobject thiz, const jlong handle, jstring name, jlong value) { +JNIEXPORT jobject JNICALL Java_dev_silenium_multimedia_core_mpv_MPVKt_setPropertyLongN( + JNIEnv *env, jobject thiz, const jlong handle, jstring name, jlong value) { const char *nameStr = env->GetStringUTFChars(name, nullptr); const auto ret = mpv_set_property(reinterpret_cast(handle), nameStr, MPV_FORMAT_INT64, &value); env->ReleaseStringUTFChars(name, nameStr); @@ -478,7 +557,8 @@ JNIEXPORT jobject JNICALL Java_dev_silenium_multimedia_core_mpv_MPVKt_setPropert return resultSuccess(env); } -JNIEXPORT jobject JNICALL Java_dev_silenium_multimedia_core_mpv_MPVKt_setPropertyDoubleN(JNIEnv *env, jobject thiz, const jlong handle, jstring name, jdouble value) { +JNIEXPORT jobject JNICALL Java_dev_silenium_multimedia_core_mpv_MPVKt_setPropertyDoubleN( + JNIEnv *env, jobject thiz, const jlong handle, jstring name, jdouble value) { const char *nameStr = env->GetStringUTFChars(name, nullptr); const auto ret = mpv_set_property(reinterpret_cast(handle), nameStr, MPV_FORMAT_DOUBLE, &value); env->ReleaseStringUTFChars(name, nameStr); @@ -488,7 +568,8 @@ JNIEXPORT jobject JNICALL Java_dev_silenium_multimedia_core_mpv_MPVKt_setPropert return resultSuccess(env); } -JNIEXPORT jobject JNICALL Java_dev_silenium_multimedia_core_mpv_MPVKt_setPropertyFlagN(JNIEnv *env, jobject thiz, const jlong handle, jstring name, jboolean value) { +JNIEXPORT jobject JNICALL Java_dev_silenium_multimedia_core_mpv_MPVKt_setPropertyFlagN( + JNIEnv *env, jobject thiz, const jlong handle, jstring name, jboolean value) { const char *nameStr = env->GetStringUTFChars(name, nullptr); const auto ret = mpv_set_property(reinterpret_cast(handle), nameStr, MPV_FORMAT_FLAG, &value); env->ReleaseStringUTFChars(name, nameStr); @@ -498,9 +579,11 @@ JNIEXPORT jobject JNICALL Java_dev_silenium_multimedia_core_mpv_MPVKt_setPropert return resultSuccess(env); } -JNIEXPORT jobject JNICALL Java_dev_silenium_multimedia_core_mpv_MPVKt_observePropertyStringN(JNIEnv *env, jobject thiz, const jlong handle, jstring name, const jlong replyUserdata) { +JNIEXPORT jobject JNICALL Java_dev_silenium_multimedia_core_mpv_MPVKt_observePropertyStringN( + JNIEnv *env, jobject thiz, const jlong handle, jstring name, const jlong replyUserdata) { const char *nameStr = env->GetStringUTFChars(name, nullptr); - const auto ret = mpv_observe_property(reinterpret_cast(handle), replyUserdata, nameStr, MPV_FORMAT_STRING); + const auto ret = mpv_observe_property(reinterpret_cast(handle), replyUserdata, nameStr, + MPV_FORMAT_STRING); env->ReleaseStringUTFChars(name, nameStr); if (ret < 0) { return mpvResultFailure(env, "mpv_observe_property", ret); @@ -508,9 +591,11 @@ JNIEXPORT jobject JNICALL Java_dev_silenium_multimedia_core_mpv_MPVKt_observePro return resultSuccess(env); } -JNIEXPORT jobject JNICALL Java_dev_silenium_multimedia_core_mpv_MPVKt_observePropertyLongN(JNIEnv *env, jobject thiz, const jlong handle, jstring name, const jlong replyUserdata) { +JNIEXPORT jobject JNICALL Java_dev_silenium_multimedia_core_mpv_MPVKt_observePropertyLongN( + JNIEnv *env, jobject thiz, const jlong handle, jstring name, const jlong replyUserdata) { const char *nameStr = env->GetStringUTFChars(name, nullptr); - const auto ret = mpv_observe_property(reinterpret_cast(handle), replyUserdata, nameStr, MPV_FORMAT_INT64); + const auto ret = mpv_observe_property(reinterpret_cast(handle), replyUserdata, nameStr, + MPV_FORMAT_INT64); env->ReleaseStringUTFChars(name, nameStr); if (ret < 0) { return mpvResultFailure(env, "mpv_observe_property", ret); @@ -518,9 +603,11 @@ JNIEXPORT jobject JNICALL Java_dev_silenium_multimedia_core_mpv_MPVKt_observePro return resultSuccess(env); } -JNIEXPORT jobject JNICALL Java_dev_silenium_multimedia_core_mpv_MPVKt_observePropertyDoubleN(JNIEnv *env, jobject thiz, const jlong handle, jstring name, const jlong replyUserdata) { +JNIEXPORT jobject JNICALL Java_dev_silenium_multimedia_core_mpv_MPVKt_observePropertyDoubleN( + JNIEnv *env, jobject thiz, const jlong handle, jstring name, const jlong replyUserdata) { const char *nameStr = env->GetStringUTFChars(name, nullptr); - const auto ret = mpv_observe_property(reinterpret_cast(handle), replyUserdata, nameStr, MPV_FORMAT_DOUBLE); + const auto ret = mpv_observe_property(reinterpret_cast(handle), replyUserdata, nameStr, + MPV_FORMAT_DOUBLE); env->ReleaseStringUTFChars(name, nameStr); if (ret < 0) { return mpvResultFailure(env, "mpv_observe_property", ret); @@ -528,9 +615,11 @@ JNIEXPORT jobject JNICALL Java_dev_silenium_multimedia_core_mpv_MPVKt_observePro return resultSuccess(env); } -JNIEXPORT jobject JNICALL Java_dev_silenium_multimedia_core_mpv_MPVKt_observePropertyFlagN(JNIEnv *env, jobject thiz, const jlong handle, jstring name, const jlong replyUserdata) { +JNIEXPORT jobject JNICALL Java_dev_silenium_multimedia_core_mpv_MPVKt_observePropertyFlagN( + JNIEnv *env, jobject thiz, const jlong handle, jstring name, const jlong replyUserdata) { const char *nameStr = env->GetStringUTFChars(name, nullptr); - const auto ret = mpv_observe_property(reinterpret_cast(handle), replyUserdata, nameStr, MPV_FORMAT_FLAG); + const auto ret = mpv_observe_property(reinterpret_cast(handle), replyUserdata, nameStr, + MPV_FORMAT_FLAG); env->ReleaseStringUTFChars(name, nameStr); if (ret < 0) { return mpvResultFailure(env, "mpv_observe_property", ret); @@ -538,14 +627,16 @@ JNIEXPORT jobject JNICALL Java_dev_silenium_multimedia_core_mpv_MPVKt_observePro return resultSuccess(env); } -JNIEXPORT jobject JNICALL Java_dev_silenium_multimedia_core_mpv_MPVKt_unobservePropertyN(JNIEnv *env, jobject thiz, const jlong handle, const jlong replyUserdata) { +JNIEXPORT jobject JNICALL Java_dev_silenium_multimedia_core_mpv_MPVKt_unobservePropertyN( + JNIEnv *env, jobject thiz, const jlong handle, const jlong replyUserdata) { if (const auto ret = mpv_unobserve_property(reinterpret_cast(handle), replyUserdata); ret < 0) { return mpvResultFailure(env, "mpv_unobserve_property", ret); } return resultSuccess(env); } -JNIEXPORT jobject JNICALL Java_dev_silenium_multimedia_core_mpv_MPVKt_initializeN(JNIEnv *env, jobject thiz, const jlong handle) { +JNIEXPORT jobject JNICALL Java_dev_silenium_multimedia_core_mpv_MPVKt_initializeN( + JNIEnv *env, jobject thiz, const jlong handle) { if (const auto ret = mpv_initialize(reinterpret_cast(handle)); ret < 0) { return mpvResultFailure(env, "mpv_initialize", ret); } @@ -555,23 +646,17 @@ JNIEXPORT jobject JNICALL Java_dev_silenium_multimedia_core_mpv_MPVKt_initialize struct RenderContext { mpv_render_context *handle; jobject gref; - Display *display; + std::shared_ptr platformContext; }; // Rendering -JNIEXPORT jobject JNICALL Java_dev_silenium_multimedia_core_mpv_MPVKt_createRenderN(JNIEnv *env, jobject thiz, const jlong mpvHandle, jobject self, const jboolean advancedControl) { +JNIEXPORT jobject JNICALL Java_dev_silenium_multimedia_core_mpv_MPVKt_createRenderN( + JNIEnv *env, jobject thiz, const jlong mpvHandle, jobject self, const jboolean advancedControl) { std::vector params{ - {MPV_RENDER_PARAM_API_TYPE, const_cast(MPV_RENDER_API_TYPE_OPENGL)}, + {MPV_RENDER_PARAM_API_TYPE, const_cast(MPV_RENDER_API_TYPE_OPENGL)}, }; - Display *display{nullptr}; - if (const auto glxDisplay = glXGetCurrentDisplay(); glxDisplay != nullptr) { - params.emplace_back(MPV_RENDER_PARAM_X11_DISPLAY, glxDisplay); - } else if (const auto eglDisplay = eglGetCurrentDisplay(); eglDisplay != EGL_NO_DISPLAY) { - // Compose always runs on X11 - display = XOpenDisplay(nullptr); - params.emplace_back(MPV_RENDER_PARAM_X11_DISPLAY, display); - } + const auto platformContext = populatePlatformMpvParams(env, params); JavaVM *jvm; if (const auto ret = env->GetJavaVM(&jvm); ret != JNI_OK) { @@ -581,87 +666,91 @@ JNIEXPORT jobject JNICALL Java_dev_silenium_multimedia_core_mpv_MPVKt_createRend const auto object = env->NewGlobalRef(self); mpv_opengl_init_params gl_params{ - .get_proc_address = fnptr([jvm](void *opaque, const char *name) -> void * { - const auto javaRender = static_cast(opaque); - JNIEnv *jni_env; - const auto res = jvm->AttachCurrentThread(reinterpret_cast(&jni_env), nullptr); - if (res != JNI_OK) { - std::cerr << "Failed to attach current thread" << std::endl; - return nullptr; - } + .get_proc_address = fnptr([jvm](void *opaque, const char *name) -> void * { + const auto javaRender = static_cast(opaque); + JNIEnv *jni_env; + const auto res = jvm->AttachCurrentThread(reinterpret_cast(&jni_env), nullptr); + if (res != JNI_OK) { + std::cerr << "Failed to attach current thread" << std::endl; + return nullptr; + } - const auto glProcMethod = jni_env->GetMethodID(jni_env->GetObjectClass(javaRender), "getGlProc", "(Ljava/lang/String;)J"); - if (glProcMethod == nullptr) { - std::cerr << "Method not found: getGlProc" << std::endl; - return nullptr; - } + const auto glProcMethod = jni_env->GetMethodID(jni_env->GetObjectClass(javaRender), "getGlProc", + "(Ljava/lang/String;)J"); + if (glProcMethod == nullptr) { + std::cerr << "Method not found: getGlProc" << std::endl; + return nullptr; + } - const auto nameStr = jni_env->NewStringUTF(name); - const auto ret = jni_env->CallLongMethod(javaRender, glProcMethod, nameStr); - jni_env->DeleteLocalRef(nameStr); - jvm->DetachCurrentThread(); - return reinterpret_cast(ret); - }), - .get_proc_address_ctx = object, + const auto nameStr = jni_env->NewStringUTF(name); + const auto ret = jni_env->CallLongMethod(javaRender, glProcMethod, nameStr); + jni_env->DeleteLocalRef(nameStr); + jvm->DetachCurrentThread(); + return reinterpret_cast(ret); + }), + .get_proc_address_ctx = object, }; - params.emplace_back(MPV_RENDER_PARAM_OPENGL_INIT_PARAMS, &gl_params); + params.push_back({MPV_RENDER_PARAM_OPENGL_INIT_PARAMS, &gl_params}); int advControl = advancedControl ? 1 : 0; - params.emplace_back(MPV_RENDER_PARAM_ADVANCED_CONTROL, &advControl); - params.emplace_back(MPV_RENDER_PARAM_INVALID, nullptr); + params.push_back({MPV_RENDER_PARAM_ADVANCED_CONTROL, &advControl}); + params.push_back({MPV_RENDER_PARAM_INVALID, nullptr}); mpv_render_context *handle{nullptr}; - if (const auto ret = mpv_render_context_create(&handle, reinterpret_cast(mpvHandle), params.data()); ret < 0) { + if (const auto ret = mpv_render_context_create(&handle, reinterpret_cast(mpvHandle), params.data()); + ret < 0) { env->DeleteGlobalRef(object); return mpvResultFailure(env, "mpv_render_context_create", ret); } - const auto ctx = new RenderContext{handle, object, display}; + const auto ctx = new RenderContext{handle, object, std::move(platformContext)}; mpv_render_context_set_update_callback( - handle, - fnptr([jvm](void *opaque) { - const auto render_context = static_cast(opaque); - JNIEnv *jni_env; - const auto res = jvm->AttachCurrentThread(reinterpret_cast(&jni_env), nullptr); - if (res != JNI_OK) { - std::cerr << "Failed to attach current thread" << std::endl; - return; - } - const auto updateMethod = jni_env->GetMethodID(jni_env->GetObjectClass(render_context->gref), "requestUpdate", "()V"); - if (updateMethod == nullptr) { - std::cerr << "Method not found: requestUpdate" << std::endl; - return; - } - jni_env->CallVoidMethod(render_context->gref, updateMethod); - jvm->DetachCurrentThread(); - }), - ctx); + handle, + fnptr([jvm](void *opaque) { + const auto render_context = static_cast(opaque); + JNIEnv *jni_env; + const auto res = jvm->AttachCurrentThread(reinterpret_cast(&jni_env), nullptr); + if (res != JNI_OK) { + std::cerr << "Failed to attach current thread" << std::endl; + return; + } + const auto updateMethod = jni_env->GetMethodID(jni_env->GetObjectClass(render_context->gref), + "requestUpdate", "()V"); + if (updateMethod == nullptr) { + std::cerr << "Method not found: requestUpdate" << std::endl; + return; + } + jni_env->CallVoidMethod(render_context->gref, updateMethod); + jvm->DetachCurrentThread(); + }), + ctx); return resultSuccess(env, reinterpret_cast(ctx)); } -JNIEXPORT void JNICALL Java_dev_silenium_multimedia_core_mpv_MPVKt_destroyRenderN(JNIEnv *env, jobject thiz, const jlong handle) { +JNIEXPORT void JNICALL Java_dev_silenium_multimedia_core_mpv_MPVKt_destroyRenderN( + JNIEnv *env, jobject thiz, const jlong handle) { const auto context = reinterpret_cast(handle); mpv_render_context_free(context->handle); env->DeleteGlobalRef(context->gref); - if (context->display != nullptr) { - XCloseDisplay(context->display); - } + context->platformContext.reset(); delete context; } -JNIEXPORT jobject JNICALL Java_dev_silenium_multimedia_core_mpv_MPVKt_renderN(JNIEnv *env, jobject thiz, const jlong handle, const GLint fbo, const jint width, const jint height, const jint glInternalFormat) { +JNIEXPORT jobject JNICALL Java_dev_silenium_multimedia_core_mpv_MPVKt_renderN( + JNIEnv *env, jobject thiz, const jlong handle, const GLint fbo, const jint width, const jint height, + const jint glInternalFormat) { const auto context = reinterpret_cast(handle); const auto flags = mpv_render_context_update(context->handle); if (flags & MPV_RENDER_UPDATE_FRAME) { mpv_opengl_fbo fboData{ - .fbo = fbo, - .w = width, - .h = height, - .internal_format = glInternalFormat, + .fbo = fbo, + .w = width, + .h = height, + .internal_format = glInternalFormat, }; int flipY{0}; mpv_render_param params[]{ - {MPV_RENDER_PARAM_OPENGL_FBO, &fboData}, - {MPV_RENDER_PARAM_FLIP_Y, &flipY}, - {MPV_RENDER_PARAM_INVALID, nullptr}, + {MPV_RENDER_PARAM_OPENGL_FBO, &fboData}, + {MPV_RENDER_PARAM_FLIP_Y, &flipY}, + {MPV_RENDER_PARAM_INVALID, nullptr}, }; if (const auto ret = mpv_render_context_render(context->handle, params); ret < 0) { return mpvResultFailure(env, "mpv_render_context_render", ret); diff --git a/native/src/mpv/mpv_linux.cpp b/native/src/mpv/mpv_linux.cpp new file mode 100644 index 0000000..abe29cd --- /dev/null +++ b/native/src/mpv/mpv_linux.cpp @@ -0,0 +1,31 @@ +#include "mpv_platform.hpp" + +#include +#include + +class LinuxMpvPlatformContext : public MpvPlatformContext { +public: + explicit LinuxMpvPlatformContext(Display *x_display) : display(x_display) { + } + + ~LinuxMpvPlatformContext() override { + if (display != nullptr) { + XCloseDisplay(display); + } + } + +private: + Display *display; +}; + +std::shared_ptr populatePlatformMpvParams(JNIEnv *env, std::vector ¶ms) { + Display *display{nullptr}; + if (const auto glxDisplay = glXGetCurrentDisplay(); glxDisplay != nullptr) { + params.push_back({MPV_RENDER_PARAM_X11_DISPLAY, glxDisplay}); + } else if (const auto eglDisplay = eglGetCurrentDisplay(); eglDisplay != EGL_NO_DISPLAY) { + // Compose always runs on X11 + display = XOpenDisplay(nullptr); + params.push_back({MPV_RENDER_PARAM_X11_DISPLAY, display}); + } + return std::make_shared(display); +} diff --git a/native/src/mpv/mpv_platform.hpp b/native/src/mpv/mpv_platform.hpp new file mode 100644 index 0000000..51e6818 --- /dev/null +++ b/native/src/mpv/mpv_platform.hpp @@ -0,0 +1,16 @@ +#ifndef NATIVE_MPV_PLATFORM_HPP +#define NATIVE_MPV_PLATFORM_HPP + +#include +#include +#include +#include + +class MpvPlatformContext { +public: + virtual ~MpvPlatformContext() = default; +}; + +std::shared_ptr populatePlatformMpvParams(JNIEnv *env, std::vector ¶ms); + +#endif //NATIVE_MPV_PLATFORM_HPP diff --git a/native/src/mpv/mpv_windows.cpp b/native/src/mpv/mpv_windows.cpp new file mode 100644 index 0000000..5fac235 --- /dev/null +++ b/native/src/mpv/mpv_windows.cpp @@ -0,0 +1,12 @@ +#include "mpv_platform.hpp" + +class WindowsMpvPlatformContext : public MpvPlatformContext { +public: + WindowsMpvPlatformContext() = default; + + ~WindowsMpvPlatformContext() override = default; +}; + +std::shared_ptr populatePlatformMpvParams(JNIEnv *env, std::vector ¶ms) { + return std::make_shared(); +} diff --git a/native/src/util/Errors.cpp b/native/src/util/Errors.cpp new file mode 100644 index 0000000..381c3c3 --- /dev/null +++ b/native/src/util/Errors.cpp @@ -0,0 +1,93 @@ +// +// Created by silenium-dev on 7/21/24. +// + +#include +#include +#include + +#ifdef TARGET_LINUX +#include +#include +#define CASE_STR(value) case value: return #value; + +const char *eglGetErrorString(const long error) { + switch (error) { + CASE_STR(EGL_SUCCESS) + CASE_STR(EGL_NOT_INITIALIZED) + CASE_STR(EGL_BAD_ACCESS) + CASE_STR(EGL_BAD_ALLOC) + CASE_STR(EGL_BAD_ATTRIBUTE) + CASE_STR(EGL_BAD_CONTEXT) + CASE_STR(EGL_BAD_CONFIG) + CASE_STR(EGL_BAD_CURRENT_SURFACE) + CASE_STR(EGL_BAD_DISPLAY) + CASE_STR(EGL_BAD_SURFACE) + CASE_STR(EGL_BAD_MATCH) + CASE_STR(EGL_BAD_PARAMETER) + CASE_STR(EGL_BAD_NATIVE_PIXMAP) + CASE_STR(EGL_BAD_NATIVE_WINDOW) + CASE_STR(EGL_CONTEXT_LOST) + default: + return "Unknown"; + } +} + +#undef CASE_STR +#endif + +extern "C" { +JNIEXPORT jstring JNICALL Java_dev_silenium_multimedia_core_util_ErrorsKt_mpvErrorStringN( + JNIEnv *env, + jobject thiz, + const jint error) { + return env->NewStringUTF(mpv_error_string(error)); +} + +JNIEXPORT jstring JNICALL Java_dev_silenium_multimedia_core_util_ErrorsKt_glErrorStringN( + JNIEnv *env, + jobject thiz, + const jint error) { + switch (error) { + case GL_NO_ERROR: + return env->NewStringUTF("GL_NO_ERROR"); + case GL_INVALID_ENUM: + return env->NewStringUTF("GL_INVALID_ENUM"); + case GL_INVALID_VALUE: + return env->NewStringUTF("GL_INVALID_VALUE"); + case GL_INVALID_OPERATION: + return env->NewStringUTF("GL_INVALID_OPERATION"); +#ifdef GL_INVALID_FRAMEBUFFER_OPERATION + case GL_INVALID_FRAMEBUFFER_OPERATION: + return env->NewStringUTF("GL_INVALID_FRAMEBUFFER_OPERATION"); +#endif + case GL_OUT_OF_MEMORY: + return env->NewStringUTF("GL_OUT_OF_MEMORY"); + case GL_STACK_UNDERFLOW: + return env->NewStringUTF("GL_STACK_UNDERFLOW"); + case GL_STACK_OVERFLOW: + return env->NewStringUTF("GL_STACK_OVERFLOW"); + default: + return env->NewStringUTF("Unknown"); + } +} + +#ifdef TARGET_LINUX + +JNIEXPORT jstring JNICALL Java_dev_silenium_multimedia_util_ErrorsKt_eglErrorStringN( + JNIEnv *env, + jobject thiz, + jint error +) { + return env->NewStringUTF(eglGetErrorString(error)); +} + +JNIEXPORT jstring JNICALL Java_dev_silenium_multimedia_util_ErrorsKt_vaErrorStringN( + JNIEnv *env, + jobject thiz, + jint error +) { + return env->NewStringUTF(vaErrorStr(error)); +} +#endif +} diff --git a/native/subprojects.tpl/linux/mpv.wrap b/native/subprojects.tpl/linux/mpv.wrap new file mode 100644 index 0000000..c70e148 --- /dev/null +++ b/native/subprojects.tpl/linux/mpv.wrap @@ -0,0 +1,5 @@ +[wrap-file] +directory = mpv +source_filename = mpv.tar +patch_directory = mpv +lead_directory_missing = true diff --git a/native/subprojects.tpl/linux/packagefiles/mpv/subprojects/expat.wrap b/native/subprojects.tpl/linux/packagefiles/mpv/subprojects/expat.wrap new file mode 100644 index 0000000..cadc76f --- /dev/null +++ b/native/subprojects.tpl/linux/packagefiles/mpv/subprojects/expat.wrap @@ -0,0 +1,7 @@ +[wrap-file] +directory = expat +source_filename = expat.tar +lead_directory_missing = true + +[provide] +expat = expat_dep diff --git a/native/subprojects.tpl/linux/packagefiles/mpv/subprojects/ffmpeg.wrap b/native/subprojects.tpl/linux/packagefiles/mpv/subprojects/ffmpeg.wrap new file mode 100644 index 0000000..0c28952 --- /dev/null +++ b/native/subprojects.tpl/linux/packagefiles/mpv/subprojects/ffmpeg.wrap @@ -0,0 +1,8 @@ +[wrap-file] +directory = ffmpeg +source_filename = ffmpeg.tar +patch_directory = ffmpeg +lead_directory_missing = true + +[provide] +dependency_names = libavcodec, libavfilter, libavformat, libavutil, libswresample, libswscale diff --git a/native/subprojects.tpl/linux/packagefiles/mpv/subprojects/fmt.wrap b/native/subprojects.tpl/linux/packagefiles/mpv/subprojects/fmt.wrap new file mode 100644 index 0000000..e08b9c8 --- /dev/null +++ b/native/subprojects.tpl/linux/packagefiles/mpv/subprojects/fmt.wrap @@ -0,0 +1,9 @@ +[wrap-file] +directory = fmt +source_filename = fmt.tar +method = cmake +diff_files = fmt/pic.patch +lead_directory_missing = true + +[provide] +fmt = fmt_dep diff --git a/native/subprojects.tpl/linux/packagefiles/mpv/subprojects/freetype2.wrap b/native/subprojects.tpl/linux/packagefiles/mpv/subprojects/freetype2.wrap new file mode 100644 index 0000000..0bffe56 --- /dev/null +++ b/native/subprojects.tpl/linux/packagefiles/mpv/subprojects/freetype2.wrap @@ -0,0 +1,6 @@ +[wrap-file] +source_filename = freetype2.tar +lead_directory_missing = true + +[provide] +dependency_names = freetype2 diff --git a/native/subprojects.tpl/linux/packagefiles/mpv/subprojects/fribidi.wrap b/native/subprojects.tpl/linux/packagefiles/mpv/subprojects/fribidi.wrap new file mode 100644 index 0000000..2173e27 --- /dev/null +++ b/native/subprojects.tpl/linux/packagefiles/mpv/subprojects/fribidi.wrap @@ -0,0 +1,3 @@ +[wrap-file] +source_filename = fribidi.tar +lead_directory_missing = true diff --git a/native/subprojects.tpl/linux/packagefiles/mpv/subprojects/google-brotli.wrap b/native/subprojects.tpl/linux/packagefiles/mpv/subprojects/google-brotli.wrap new file mode 100644 index 0000000..5d09fb2 --- /dev/null +++ b/native/subprojects.tpl/linux/packagefiles/mpv/subprojects/google-brotli.wrap @@ -0,0 +1,9 @@ +[wrap-file] +directory = brotli +source_filename = brotli.tar +lead_directory_missing = true +patch_filename = brotli-patch.zip + +[provide] +program_names = brotli +dependency_names = libbrotlicommon, libbrotlidec, libbrotlienc diff --git a/native/subprojects.tpl/linux/packagefiles/mpv/subprojects/harfbuzz.wrap b/native/subprojects.tpl/linux/packagefiles/mpv/subprojects/harfbuzz.wrap new file mode 100644 index 0000000..3f66e07 --- /dev/null +++ b/native/subprojects.tpl/linux/packagefiles/mpv/subprojects/harfbuzz.wrap @@ -0,0 +1,7 @@ +[wrap-file] +source_filename = harfbuzz.tar +lead_directory_missing = true +patch_directory = harfbuzz + +[provide] +dependency_names = harfbuzz, harfbuzz-cairo, harfbuzz-gobject, harfbuzz-icu, harfbuzz-subset diff --git a/native/subprojects.tpl/linux/packagefiles/mpv/subprojects/libass.wrap b/native/subprojects.tpl/linux/packagefiles/mpv/subprojects/libass.wrap new file mode 100644 index 0000000..1201ab8 --- /dev/null +++ b/native/subprojects.tpl/linux/packagefiles/mpv/subprojects/libass.wrap @@ -0,0 +1,4 @@ +[wrap-file] +source_filename = libass.tar +lead_directory_missing = true +patch_directory = libass diff --git a/native/subprojects.tpl/linux/packagefiles/mpv/subprojects/libdbus.wrap.disabled b/native/subprojects.tpl/linux/packagefiles/mpv/subprojects/libdbus.wrap.disabled new file mode 100644 index 0000000..6e6b101 --- /dev/null +++ b/native/subprojects.tpl/linux/packagefiles/mpv/subprojects/libdbus.wrap.disabled @@ -0,0 +1,8 @@ +[wrap-git] +url = https://gitlab.freedesktop.org/dbus/dbus.git +revision = dbus-1.16.2 +depth = 1 +clone-recursive = true + +[provide] +dependency_names = dbus-1 diff --git a/native/subprojects.tpl/linux/packagefiles/mpv/subprojects/libdisplay-info.wrap b/native/subprojects.tpl/linux/packagefiles/mpv/subprojects/libdisplay-info.wrap new file mode 100644 index 0000000..3bd40ae --- /dev/null +++ b/native/subprojects.tpl/linux/packagefiles/mpv/subprojects/libdisplay-info.wrap @@ -0,0 +1,4 @@ +[wrap-file] +source_filename = libdisplay-info.tar +lead_directory_missing = true +diff_files = libdisplay-info/hwdata.patch diff --git a/native/subprojects.tpl/linux/packagefiles/mpv/subprojects/libjpeg-turbo.wrap b/native/subprojects.tpl/linux/packagefiles/mpv/subprojects/libjpeg-turbo.wrap new file mode 100644 index 0000000..4c397fd --- /dev/null +++ b/native/subprojects.tpl/linux/packagefiles/mpv/subprojects/libjpeg-turbo.wrap @@ -0,0 +1,7 @@ +[wrap-file] +source_filename = libjpeg-turbo.tar +lead_directory_missing = true +patch_directory = libjpeg-turbo + +[provide] +dependency_names = libjpeg, libturbojpeg diff --git a/native/subprojects.tpl/linux/packagefiles/mpv/subprojects/libpipewire-0.3.wrap.disabled b/native/subprojects.tpl/linux/packagefiles/mpv/subprojects/libpipewire-0.3.wrap.disabled new file mode 100644 index 0000000..4691d10 --- /dev/null +++ b/native/subprojects.tpl/linux/packagefiles/mpv/subprojects/libpipewire-0.3.wrap.disabled @@ -0,0 +1,8 @@ +[wrap-git] +url = https://gitlab.freedesktop.org/pipewire/pipewire.git +revision = 1.4.9 +depth = 1 +clone-recursive = true + +[provide] +dependency_names = libpipewire-0.3 diff --git a/native/subprojects.tpl/linux/packagefiles/mpv/subprojects/libplacebo.wrap b/native/subprojects.tpl/linux/packagefiles/mpv/subprojects/libplacebo.wrap new file mode 100644 index 0000000..ea56418 --- /dev/null +++ b/native/subprojects.tpl/linux/packagefiles/mpv/subprojects/libplacebo.wrap @@ -0,0 +1,3 @@ +[wrap-file] +source_filename = libplacebo.tar +lead_directory_missing = true diff --git a/native/subprojects.tpl/linux/packagefiles/mpv/subprojects/libpng.wrap b/native/subprojects.tpl/linux/packagefiles/mpv/subprojects/libpng.wrap new file mode 100644 index 0000000..6b83165 --- /dev/null +++ b/native/subprojects.tpl/linux/packagefiles/mpv/subprojects/libpng.wrap @@ -0,0 +1,6 @@ +[wrap-file] +source_filename = libpng.tar +lead_directory_missing = true + +[provide] +libpng = libpng_dep diff --git a/native/subprojects.tpl/linux/packagefiles/mpv/subprojects/libuchardet.wrap b/native/subprojects.tpl/linux/packagefiles/mpv/subprojects/libuchardet.wrap new file mode 100644 index 0000000..6fd029b --- /dev/null +++ b/native/subprojects.tpl/linux/packagefiles/mpv/subprojects/libuchardet.wrap @@ -0,0 +1,6 @@ +[wrap-file] +source_filename = libuchardet.tar +lead_directory_missing = true + +[provide] +uchardet = libuchardet_dep diff --git a/native/subprojects.tpl/linux/packagefiles/mpv/subprojects/openal-soft.wrap b/native/subprojects.tpl/linux/packagefiles/mpv/subprojects/openal-soft.wrap new file mode 100644 index 0000000..e583a11 --- /dev/null +++ b/native/subprojects.tpl/linux/packagefiles/mpv/subprojects/openal-soft.wrap @@ -0,0 +1,6 @@ +[wrap-file] +source_filename = openal-soft.tar +lead_directory_missing = true + +[provide] +openal = openal_dep diff --git a/native/subprojects.tpl/linux/packagefiles/mpv/subprojects/packagefiles/ffmpeg/collect_versions.sh b/native/subprojects.tpl/linux/packagefiles/mpv/subprojects/packagefiles/ffmpeg/collect_versions.sh new file mode 100644 index 0000000..c620567 --- /dev/null +++ b/native/subprojects.tpl/linux/packagefiles/mpv/subprojects/packagefiles/ffmpeg/collect_versions.sh @@ -0,0 +1,9 @@ +#!/usr/bin/env bash + +for shared_object in "${MESON_SOURCE_ROOT}/${MESON_SUBDIR}"/lib/*.so.*.*.*; do + filename=$(basename "$shared_object") + lib_name=$(basename "${filename//\.so.*/}") + lib_name="${lib_name//lib/}" + lib_version=${filename//*.so\./} + echo "${lib_name}:${lib_version}" +done diff --git a/native/subprojects.tpl/linux/packagefiles/mpv/subprojects/packagefiles/ffmpeg/meson.build b/native/subprojects.tpl/linux/packagefiles/mpv/subprojects/packagefiles/ffmpeg/meson.build new file mode 100644 index 0000000..791782c --- /dev/null +++ b/native/subprojects.tpl/linux/packagefiles/mpv/subprojects/packagefiles/ffmpeg/meson.build @@ -0,0 +1,26 @@ +project('ffmpeg', 'c') + +cc = meson.get_compiler('c') + +versions_command = run_command('bash', 'collect_versions.sh', capture : true, check : true) +versions = versions_command.stdout().strip().split('\n') + +library_names = ['avcodec', 'avfilter', 'avformat', 'avutil', 'swresample', 'swscale'] + +foreach lib_name : library_names + foreach version : versions + version_name = version.split(':')[0].strip() + version_value = version.split(':')[1].strip() + + if version_name == lib_name + message(version) + dep = declare_dependency( + version : version_value, + dependencies : cc.find_library(lib_name, dirs : meson.current_source_dir() + '/lib'), + include_directories : include_directories('include'), + ) + meson.override_dependency('lib' + lib_name, dep) + break + endif + endforeach +endforeach diff --git a/native/subprojects.tpl/linux/packagefiles/mpv/subprojects/packagefiles/fmt/pic.patch b/native/subprojects.tpl/linux/packagefiles/mpv/subprojects/packagefiles/fmt/pic.patch new file mode 100644 index 0000000..eca72f8 --- /dev/null +++ b/native/subprojects.tpl/linux/packagefiles/mpv/subprojects/packagefiles/fmt/pic.patch @@ -0,0 +1,13 @@ +diff --git a/CMakeLists.txt b/CMakeLists.txt +index 3c05513..5306154 100644 +--- a/CMakeLists.txt ++++ b/CMakeLists.txt +@@ -5,6 +5,8 @@ if (${CMAKE_VERSION} VERSION_LESS 3.12) + cmake_policy(VERSION ${CMAKE_MAJOR_VERSION}.${CMAKE_MINOR_VERSION}) + endif () + ++set(CMAKE_POSITION_INDEPENDENT_CODE ON) ++ + # Determine if fmt is built as a subproject (using add_subdirectory) + # or if it is the master project. + if (NOT DEFINED FMT_MASTER_PROJECT) diff --git a/native/subprojects.tpl/linux/packagefiles/mpv/subprojects/packagefiles/harfbuzz/subprojects/icu.wrap b/native/subprojects.tpl/linux/packagefiles/mpv/subprojects/packagefiles/harfbuzz/subprojects/icu.wrap new file mode 100644 index 0000000..134a005 --- /dev/null +++ b/native/subprojects.tpl/linux/packagefiles/mpv/subprojects/packagefiles/harfbuzz/subprojects/icu.wrap @@ -0,0 +1,3 @@ +[wrap-file] +directory = icu +source_filename = icu.tar diff --git a/native/subprojects.tpl/linux/packagefiles/mpv/subprojects/packagefiles/libass/subprojects/libfontconfig.wrap b/native/subprojects.tpl/linux/packagefiles/mpv/subprojects/packagefiles/libass/subprojects/libfontconfig.wrap new file mode 100644 index 0000000..3f1523c --- /dev/null +++ b/native/subprojects.tpl/linux/packagefiles/mpv/subprojects/packagefiles/libass/subprojects/libfontconfig.wrap @@ -0,0 +1,8 @@ +[wrap-file] +directory = libfontconfig +source_filename = libfontconfig.tar +lead_directory_missing = true +patch_directory = libfontconfig + +[provide] +fontconfig = fontconfig_dep diff --git a/native/subprojects.tpl/linux/packagefiles/mpv/subprojects/packagefiles/libass/subprojects/packagefiles/libfontconfig/subprojects/gperf.wrap b/native/subprojects.tpl/linux/packagefiles/mpv/subprojects/packagefiles/libass/subprojects/packagefiles/libfontconfig/subprojects/gperf.wrap new file mode 100644 index 0000000..082a2a5 --- /dev/null +++ b/native/subprojects.tpl/linux/packagefiles/mpv/subprojects/packagefiles/libass/subprojects/packagefiles/libfontconfig/subprojects/gperf.wrap @@ -0,0 +1,7 @@ +[wrap-file] +directory=gperf +source_filename=gperf.tar +lead_directory_missing = true + +[provide] +program_names=gperf diff --git a/native/subprojects.tpl/linux/packagefiles/mpv/subprojects/packagefiles/libass/subprojects/packagefiles/libfontconfig/subprojects/libxml2.wrap b/native/subprojects.tpl/linux/packagefiles/mpv/subprojects/packagefiles/libass/subprojects/packagefiles/libfontconfig/subprojects/libxml2.wrap new file mode 100644 index 0000000..6576d03 --- /dev/null +++ b/native/subprojects.tpl/linux/packagefiles/mpv/subprojects/packagefiles/libass/subprojects/packagefiles/libfontconfig/subprojects/libxml2.wrap @@ -0,0 +1,7 @@ +[wrap-file] +directory = libxml2 +source_filename = libxml2.tar +lead_directory_missing = true + +[provide] +libxml-2.0 = libxml2_dep diff --git a/native/subprojects.tpl/linux/packagefiles/mpv/subprojects/packagefiles/libdisplay-info/hwdata.patch b/native/subprojects.tpl/linux/packagefiles/mpv/subprojects/packagefiles/libdisplay-info/hwdata.patch new file mode 100644 index 0000000..5eed955 --- /dev/null +++ b/native/subprojects.tpl/linux/packagefiles/mpv/subprojects/packagefiles/libdisplay-info/hwdata.patch @@ -0,0 +1,13 @@ +diff --git a/meson.build b/meson.build +index 4012669..7bcf805 100644 +--- a/meson.build ++++ b/meson.build +@@ -16,7 +16,7 @@ version_major = version.split('.')[0] + version_minor = version.split('.')[1] + assert(version_major == '0') + +-dep_hwdata = dependency('hwdata', required: false, native: true) ++dep_hwdata = dependency('hwdata', required: false) + if dep_hwdata.found() + hwdata_dir = dep_hwdata.get_variable(pkgconfig: 'pkgdatadir') + pnp_ids = files(hwdata_dir / 'pnp.ids') diff --git a/native/subprojects.tpl/linux/packagefiles/mpv/subprojects/packagefiles/libjpeg-turbo/LICENSE.build b/native/subprojects.tpl/linux/packagefiles/mpv/subprojects/packagefiles/libjpeg-turbo/LICENSE.build new file mode 100644 index 0000000..b59833d --- /dev/null +++ b/native/subprojects.tpl/linux/packagefiles/mpv/subprojects/packagefiles/libjpeg-turbo/LICENSE.build @@ -0,0 +1,19 @@ +Copyright (c) 2021 The Meson development team + +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. diff --git a/native/subprojects.tpl/linux/packagefiles/mpv/subprojects/packagefiles/libjpeg-turbo/meson.build b/native/subprojects.tpl/linux/packagefiles/mpv/subprojects/packagefiles/libjpeg-turbo/meson.build new file mode 100644 index 0000000..e84a024 --- /dev/null +++ b/native/subprojects.tpl/linux/packagefiles/mpv/subprojects/packagefiles/libjpeg-turbo/meson.build @@ -0,0 +1,150 @@ +project( + 'libjpeg-turbo', + 'c', + version : '3.1.2', + license : 'BSD-3-Clause AND IJG', + meson_version : '>= 0.64.0', +) + +add_project_arguments('-D_POSIX_C_SOURCE=200809L', language : 'c') +add_project_arguments('-D_DEFAULT_SOURCE', language : 'c') + +pkg = import('pkgconfig') +python = import('python').find_installation() + +cc = meson.get_compiler('c') +host_system = host_machine.system() +host_cpu = host_machine.cpu_family() + +cdata = configuration_data() +cdata.set('CMAKE_PROJECT_NAME', meson.project_name()) +cdata.set('VERSION', meson.project_version()) +cdata.set('COPYRIGHT_YEAR', '1991-2025') + +jpeg_lib_version = 80 +so_major = jpeg_lib_version / 10 +so_age = 3 +so_minor = 2 +so_version = '@0@.@1@.@2@'.format(so_major, so_age, so_minor) +vs_defs = files('win/jpeg8.def')[0] + +version_arr = meson.project_version().split('.') +version_major = version_arr[0] +version_minor = version_arr[1] +version_revision = version_arr[2] + +# Add padding to build an integer: 2.1.0 -> 2001000 +if version_major.to_int() < 10 + version_major += '00' +elif version_major.to_int() < 100 + version_major += '0' +endif + +if version_minor.to_int() < 10 + version_minor += '00' +elif version_minor.to_int() < 100 + version_minor += '0' +endif + +cdata.set('JPEG_LIB_VERSION', jpeg_lib_version) +cdata.set( + 'LIBJPEG_TURBO_VERSION_NUMBER', + version_major + version_minor + version_revision, +) + +cdata.set('C_ARITH_CODING_SUPPORTED', true) +cdata.set('D_ARITH_CODING_SUPPORTED', true) + +check_headers = ['local.h', 'stddef.h', 'stdlib.h', 'sys/types.h'] +if cc.get_id() == 'msvc' + check_headers += 'intrin.h' +endif + +foreach header : check_headers + have_header = cc.has_header(header) + cdata.set('HAVE_' + header.underscorify().to_upper(), have_header) + cdata.set('NEED_' + header.underscorify().to_upper(), have_header) +endforeach + +has_memset = cc.has_header_symbol('string.h', 'memset') +has_memcpy = cc.has_header_symbol('string.h', 'memcpy') +cdata.set('NEED_BSD_STRINGS', not has_memset or not has_memcpy) + +size_t = cc.sizeof('size_t') +unsigned_long = cc.sizeof('unsigned long') +cdata.set('SIZE_T', size_t) +cdata.set('HAVE_UNSIGNED_CHAR', cc.sizeof('unsigned char') != -1) +cdata.set('HAVE_UNSIGNED_SHORT', cc.sizeof('unsigned short') != -1) +if size_t == unsigned_long + cdata.set('HAVE_BUILTIN_CTZL', cc.has_function('__builtin_ctzl')) +endif + +code = 'int main(void) { typedef struct undefined_structure *undef_struct_ptr; undef_struct_ptr ptr = 0; return ptr != 0; }' +cdata.set('INCOMPLETE_TYPES_BROKEN', not cc.links(code)) + +if meson.can_run_host_binaries() + code = ''' + #include + #include + static int is_shifting_signed (long arg) { + long res = arg >> 4; + if (res == -0x7F7E80CL) + return 1; /* right shift is signed */ + /* see if unsigned-shift hack will fix it. */ + /* we can't just test exact value since it depends on width of long... */ + res |= 0xFFFFFFFFL << (32-4); + if (res == -0x7F7E80CL) + return 0; /* right shift is unsigned */ + printf(\"Right shift isn't acting as I expect it to.\\\\n\"); + printf(\"I fear the JPEG software will not work at all.\\\\n\\\\n\"); + return 0; /* try it with unsigned anyway */ + } + int main (void) { + exit(is_shifting_signed(-0x7F7E80B1L)); + } + ''' + cdata.set('RIGHT_SHIFT_IS_UNSIGNED', cc.run(code).returncode() == 0) +else + cdata.set('RIGHT_SHIFT_IS_UNSIGNED', false) +endif + +p = run_command( + python, + '-c', + 'import datetime; print(datetime.datetime.now().strftime("%Y%m%d"))', + check : true, +) +cdata.set('BUILD', p.stdout().strip()) + +if cc.get_id() == 'msvc' + cdata.set('THREAD_LOCAL', '__declspec(thread)') +else + cdata.set('THREAD_LOCAL', '__thread') +endif + +cdata.set('HIDDEN', cc.has_function_attribute('visibility:hidden') ? '__attribute__((visibility("hidden")))' + : '') + +if get_option('force_inline') + if cc.get_id() == 'msvc' + cdata.set('INLINE', '__forceinline') + else + cdata.set('INLINE', 'inline __attribute__((always_inline))') + endif +else + cdata.set('INLINE', 'inline') +endif + +if host_system == 'windows' + add_project_arguments( + '-DDLLDEFINE', + language : 'c', + ) +endif + +incdir = include_directories('src') + +subdir('simd') +cdata.set('WITH_SIMD', have_simd) + +subdir('src') diff --git a/native/subprojects.tpl/linux/packagefiles/mpv/subprojects/packagefiles/libjpeg-turbo/meson_options.txt b/native/subprojects.tpl/linux/packagefiles/mpv/subprojects/packagefiles/libjpeg-turbo/meson_options.txt new file mode 100644 index 0000000..41ed4bb --- /dev/null +++ b/native/subprojects.tpl/linux/packagefiles/mpv/subprojects/packagefiles/libjpeg-turbo/meson_options.txt @@ -0,0 +1,20 @@ +option( + 'force_inline', + type : 'boolean', +) +option( + 'turbojpeg', + type : 'feature', +) +option( + 'simd', + type : 'feature', +) +option( + 'tests', + type : 'feature', +) +option( + 'neon-intrinsics', + type : 'feature', +) diff --git a/native/subprojects.tpl/linux/packagefiles/mpv/subprojects/packagefiles/libjpeg-turbo/simd/arm/meson.build b/native/subprojects.tpl/linux/packagefiles/mpv/subprojects/packagefiles/libjpeg-turbo/simd/arm/meson.build new file mode 100644 index 0000000..124b74a --- /dev/null +++ b/native/subprojects.tpl/linux/packagefiles/mpv/subprojects/packagefiles/libjpeg-turbo/simd/arm/meson.build @@ -0,0 +1,6 @@ +neon_compat_h = configure_file( + input : 'neon-compat.h.in', + output : 'neon-compat.h', + format : 'cmake', + configuration : cdata_neon, +) diff --git a/native/subprojects.tpl/linux/packagefiles/mpv/subprojects/packagefiles/libjpeg-turbo/simd/meson.build b/native/subprojects.tpl/linux/packagefiles/mpv/subprojects/packagefiles/libjpeg-turbo/simd/meson.build new file mode 100644 index 0000000..7c2306d --- /dev/null +++ b/native/subprojects.tpl/linux/packagefiles/mpv/subprojects/packagefiles/libjpeg-turbo/simd/meson.build @@ -0,0 +1,338 @@ +simd_src_x86_64 = [ + 'x86_64/jccolor-avx2.asm', + 'x86_64/jccolor-sse2.asm', + 'x86_64/jcgray-avx2.asm', + 'x86_64/jcgray-sse2.asm', + 'x86_64/jchuff-sse2.asm', + 'x86_64/jcphuff-sse2.asm', + 'x86_64/jcsample-avx2.asm', + 'x86_64/jcsample-sse2.asm', + 'x86_64/jdcolor-avx2.asm', + 'x86_64/jdcolor-sse2.asm', + 'x86_64/jdmerge-avx2.asm', + 'x86_64/jdmerge-sse2.asm', + 'x86_64/jdsample-avx2.asm', + 'x86_64/jdsample-sse2.asm', + 'x86_64/jfdctflt-sse.asm', + 'x86_64/jfdctfst-sse2.asm', + 'x86_64/jfdctint-avx2.asm', + 'x86_64/jfdctint-sse2.asm', + 'x86_64/jidctflt-sse2.asm', + 'x86_64/jidctfst-sse2.asm', + 'x86_64/jidctint-avx2.asm', + 'x86_64/jidctint-sse2.asm', + 'x86_64/jidctred-sse2.asm', + 'x86_64/jquantf-sse2.asm', + 'x86_64/jquanti-avx2.asm', + 'x86_64/jquanti-sse2.asm', + 'x86_64/jsimdcpu.asm', +] + +simd_src_i386 = [ + 'i386/jccolor-avx2.asm', + 'i386/jccolor-mmx.asm', + 'i386/jccolor-sse2.asm', + 'i386/jcgray-avx2.asm', + 'i386/jcgray-mmx.asm', + 'i386/jcgray-sse2.asm', + 'i386/jchuff-sse2.asm', + 'i386/jcphuff-sse2.asm', + 'i386/jcsample-avx2.asm', + 'i386/jcsample-mmx.asm', + 'i386/jcsample-sse2.asm', + 'i386/jdcolor-avx2.asm', + 'i386/jdcolor-mmx.asm', + 'i386/jdcolor-sse2.asm', + 'i386/jdmerge-avx2.asm', + 'i386/jdmerge-mmx.asm', + 'i386/jdmerge-sse2.asm', + 'i386/jdsample-avx2.asm', + 'i386/jdsample-mmx.asm', + 'i386/jdsample-sse2.asm', + 'i386/jfdctflt-3dn.asm', + 'i386/jfdctflt-sse.asm', + 'i386/jfdctfst-mmx.asm', + 'i386/jfdctfst-sse2.asm', + 'i386/jfdctint-avx2.asm', + 'i386/jfdctint-mmx.asm', + 'i386/jfdctint-sse2.asm', + 'i386/jidctflt-3dn.asm', + 'i386/jidctflt-sse2.asm', + 'i386/jidctflt-sse.asm', + 'i386/jidctfst-mmx.asm', + 'i386/jidctfst-sse2.asm', + 'i386/jidctint-avx2.asm', + 'i386/jidctint-mmx.asm', + 'i386/jidctint-sse2.asm', + 'i386/jidctred-mmx.asm', + 'i386/jidctred-sse2.asm', + 'i386/jquant-3dn.asm', + 'i386/jquantf-sse2.asm', + 'i386/jquanti-avx2.asm', + 'i386/jquanti-sse2.asm', + 'i386/jquant-mmx.asm', + 'i386/jquant-sse.asm', + 'i386/jsimdcpu.asm', +] + +simd_src_arm = files( + 'arm/jcgray-neon.c', + 'arm/jcphuff-neon.c', + 'arm/jcsample-neon.c', + 'arm/jdmerge-neon.c', + 'arm/jdsample-neon.c', + 'arm/jfdctfst-neon.c', + 'arm/jidctred-neon.c', + 'arm/jquanti-neon.c', +) + +simd_opt = get_option('simd') +have_simd = false +simd = [] + +if host_cpu in ['x86', 'x86_64'] + have_simd = add_languages( + 'nasm', + required : simd_opt, + native : false, + ) + if have_simd + add_project_arguments( + '-DPIC', + language : 'nasm', + ) + x64 = host_cpu == 'x86_64' + # simulate upstream's "is ELF" check by excluding non-ELF OSes + if x64 and host_machine.system() not in ['cygwin', 'darwin', 'windows'] + if cc.compiles( + ''' + #if (__CET__ & 3) == 0 + #error \"CET not enabled\" + #endif + int main(void) { return 0; } + ''', + ) + add_project_arguments( + '-D__CET__', + language : 'nasm', + ) + endif + endif + dir = x64 ? 'x86_64' : 'i386' + simd_src = x64 ? simd_src_x86_64 : simd_src_i386 + simd = static_library( + 'simd', + dir / 'jsimd.c', + simd_src, + include_directories : [incdir, 'nasm', dir], + pic : get_option('default_library') != 'static', + ) + endif +elif host_cpu in ['arm', 'aarch64'] + aarch = host_cpu == 'aarch64' ? 'aarch64' : 'aarch32' + dir = 'arm' / aarch + cdata_neon = configuration_data() + + needs_softfp_for_intrinsics = host_cpu == 'arm' and cc.compiles( + ''' + #if defined(__ARM_NEON__) || (!defined(__linux__) && !defined(ANDROID) && !defined(__ANDROID__)) + #error \"Neon run-time auto-detection will not be used\" + #endif + #if __ARM_PCS_VFP == 1 + #error \"float ABI = hard\" + #endif + #if __SOFTFP__ != 1 + #error \"float ABI = softfp\" + #endif + int main(void) { return 0; }" + ''', + name : 'does not need softfp for Neon intrinsics', + ) + neon_flags = [] + if host_cpu == 'arm' + neon_flags += ['-mfpu=neon'] + endif + if needs_softfp_for_intrinsics + neon_flags += ['-mfloatabi=softfp'] + endif + if host_cpu == 'arm' + have_simd = cc.compiles( + '''#include + int main(int argc, char **argv) { + uint16x8_t input = vdupq_n_u16((uint16_t)argc); + uint8x8_t output = vmovn_u16(input); + return (int)output[0]; + }''', + args : neon_flags, + name : 'supports Neon', + ) + if not have_simd + if simd_opt.enabled() + error('SIMD extensions not available for this architecture') + else + warning('SIMD extensions not available for this architecture') + endif + endif + else + have_simd = true + endif + + if have_simd + cdata_neon.set( + 'HAVE_VLD1_S16_X3', + cc.compiles( + '''#include + int main(int argc, char **argv) { + int16_t input[12]; + int16x4x3_t output; + int i; + for (i = 0; i < 12; i++) input[i] = (int16_t)argc; + output = vld1_s16_x3(input); + vst3_s16(input, output); + return (int)input[0]; + }''', + args : neon_flags, + name : 'supports vld1_s16_x3 intrinsic', + ), + ) + cdata_neon.set( + 'HAVE_VLD1_U16_X2', + cc.compiles( + ''' + #include + int main(int argc, char **argv) { + uint16_t input[8]; + uint16x4x2_t output; + int i; + for (i = 0; i < 8; i++) input[i] = (uint16_t)argc; + output = vld1_u16_x2(input); + vst2_u16(input, output); + return (int)input[0]; + }''', + args : neon_flags, + name : 'supports vld1_u16_x2 intrinsic', + ), + ) + cdata_neon.set( + 'HAVE_VLD1Q_U8_X4', + cc.compiles( + ''' + #include + int main(int argc, char **argv) { + uint8_t input[64]; + uint8x16x4_t output; + int i; + for (i = 0; i < 64; i++) input[i] = (uint8_t)argc; + output = vld1q_u8_x4(input); + vst4q_u8(input, output); + return (int)input[0]; + }''', + args : neon_flags, + name : 'supports vld1q_u8_x4 intrinsic', + ), + ) + + subdir('arm') + simd_src_arm += [neon_compat_h] + + # GCC 11 and earlier and some older versions of Clang do not have a full or + # optimal set of Neon intrinsics, so for performance reasons, when using those + # compilers, we default to using the older GAS implementation of the Neon SIMD + # extensions for certain algorithms. The presence or absence of the three + # intrinsics we tested above is a reasonable proxy for this, except with GCC 10 + # and 11. + + default_neon_intrinsics = cdata_neon.get('HAVE_VLD1_S16_X3') and cdata_neon.get( + 'HAVE_VLD1_U16_X2', + ) and cdata_neon.get( + 'HAVE_VLD1Q_U8_X4', + ) and ( + cc.get_id() != 'gcc' or cc.version().version_compare('>= 12.0.0') + ) + + neon_intrinsics = get_option('neon-intrinsics').disable_auto_if( + not default_neon_intrinsics, + ).allowed() + + # It is possible to run compile checks on generated files, however, + # Meson versions earlier than 1.2.0 do not set the lookup path + # correctly, causing Python to fail opening it. + # https://github.com/mesonbuild/meson/issues/11983 + if meson.version().version_compare('>= 1.2.0') and not neon_intrinsics + if (host_cpu == 'armv7') + gastest = ''' + .text + .fpu neon + .arch armv7a + .object_arch armv4 + .arm + pld [r0] + vmovn.u16 d0, q0 + ''' + else + gastest = ''' + .text + MYVAR .req x0 + movi v0.16b, #100 + mov MYVAR, #100 + .unreq MYVAR + ''' + endif + # cc.compiles() can't pass inline assembly to the C compiler + # https://github.com/mesonbuild/meson/issues/12395 + f = configure_file( + command : [ + python, + '-c', + 'import sys; print(sys.argv[1])', + '@0@'.format(gastest), + ], + output : 'gastest.S', + capture : true, + ) + if not cc.compiles( + f, + args : neon_flags, + name : 'can use the partial Neon SIMD intrinsics implementation', + ) + neon_intrinsics = true + endif + endif + + summary('Neon SIMD intrinsics', neon_intrinsics ? 'full' : 'partial') + + if neon_intrinsics + add_project_arguments( + '-DNEON_INTRINSICS', + language : 'c', + ) + simd_src_arm += files('arm/jccolor-neon.c', 'arm/jidctint-neon.c') + endif + + if neon_intrinsics or host_cpu == 'aarch64' + simd_src_arm += files('arm/jidctfst-neon.c') + endif + + if neon_intrinsics or host_cpu == 'arm' + simd_src_arm += files( + dir / 'jchuff-neon.c', + 'arm/jdcolor-neon.c', + 'arm/jfdctint-neon.c', + ) + endif + + if not neon_intrinsics + simd_src_arm += files(dir / 'jsimd_neon.S') + endif + + simd = static_library( + 'simd', + simd_src_arm + files(dir / 'jsimd.c'), + pic : get_option('default_library') != 'static', + include_directories : [incdir, 'arm', dir], + c_args : neon_flags, + ) + endif +elif simd_opt.enabled() + error('SIMD enabled, but CPU family not supported') +endif diff --git a/native/subprojects.tpl/linux/packagefiles/mpv/subprojects/packagefiles/libjpeg-turbo/src/meson.build b/native/subprojects.tpl/linux/packagefiles/mpv/subprojects/packagefiles/libjpeg-turbo/src/meson.build new file mode 100644 index 0000000..b03a0f8 --- /dev/null +++ b/native/subprojects.tpl/linux/packagefiles/mpv/subprojects/packagefiles/libjpeg-turbo/src/meson.build @@ -0,0 +1,265 @@ +jconfig_h = configure_file( + input : 'jconfig.h.in', + output : 'jconfig.h', + format : 'cmake@', + configuration : cdata, +) + +configure_file( + input : 'jconfigint.h.in', + output : 'jconfigint.h', + format : 'cmake@', + configuration : cdata, +) + +configure_file( + input : 'jversion.h.in', + output : 'jversion.h', + format : 'cmake@', + configuration : cdata, +) + +install_headers('jmorecfg.h', 'jerror.h', 'jpeglib.h', jconfig_h) + +sources = files( + 'jcapimin.c', + 'jchuff.c', + 'jcicc.c', + 'jcinit.c', + 'jclhuff.c', + 'jcmarker.c', + 'jcmaster.c', + 'jcomapi.c', + 'jcparam.c', + 'jcphuff.c', + 'jctrans.c', + 'jdapimin.c', + 'jdatadst.c', + 'jdatasrc.c', + 'jdhuff.c', + 'jdicc.c', + 'jdinput.c', + 'jdlhuff.c', + 'jdmarker.c', + 'jdmaster.c', + 'jdphuff.c', + 'jdtrans.c', + 'jerror.c', + 'jfdctflt.c', + 'jmemmgr.c', + 'jmemnobs.c', + 'jpeg_nbits.c', + 'wrapper/jcapistd-12.c', + 'wrapper/jcapistd-16.c', + 'wrapper/jcapistd-8.c', + 'wrapper/jccoefct-12.c', + 'wrapper/jccoefct-8.c', + 'wrapper/jccolor-12.c', + 'wrapper/jccolor-16.c', + 'wrapper/jccolor-8.c', + 'wrapper/jcdctmgr-12.c', + 'wrapper/jcdctmgr-8.c', + 'wrapper/jcdiffct-12.c', + 'wrapper/jcdiffct-16.c', + 'wrapper/jcdiffct-8.c', + 'wrapper/jclossls-12.c', + 'wrapper/jclossls-16.c', + 'wrapper/jclossls-8.c', + 'wrapper/jcmainct-12.c', + 'wrapper/jcmainct-16.c', + 'wrapper/jcmainct-8.c', + 'wrapper/jcprepct-12.c', + 'wrapper/jcprepct-16.c', + 'wrapper/jcprepct-8.c', + 'wrapper/jcsample-12.c', + 'wrapper/jcsample-16.c', + 'wrapper/jcsample-8.c', + 'wrapper/jdapistd-12.c', + 'wrapper/jdapistd-16.c', + 'wrapper/jdapistd-8.c', + 'wrapper/jdcoefct-12.c', + 'wrapper/jdcoefct-8.c', + 'wrapper/jdcolor-12.c', + 'wrapper/jdcolor-16.c', + 'wrapper/jdcolor-8.c', + 'wrapper/jddctmgr-12.c', + 'wrapper/jddctmgr-8.c', + 'wrapper/jddiffct-12.c', + 'wrapper/jddiffct-16.c', + 'wrapper/jddiffct-8.c', + 'wrapper/jdlossls-12.c', + 'wrapper/jdlossls-16.c', + 'wrapper/jdlossls-8.c', + 'wrapper/jdmainct-12.c', + 'wrapper/jdmainct-16.c', + 'wrapper/jdmainct-8.c', + 'wrapper/jdmerge-12.c', + 'wrapper/jdmerge-8.c', + 'wrapper/jdpostct-12.c', + 'wrapper/jdpostct-16.c', + 'wrapper/jdpostct-8.c', + 'wrapper/jdsample-12.c', + 'wrapper/jdsample-16.c', + 'wrapper/jdsample-8.c', + 'wrapper/jfdctfst-12.c', + 'wrapper/jfdctfst-8.c', + 'wrapper/jfdctint-12.c', + 'wrapper/jfdctint-8.c', + 'wrapper/jidctflt-12.c', + 'wrapper/jidctflt-8.c', + 'wrapper/jidctfst-12.c', + 'wrapper/jidctfst-8.c', + 'wrapper/jidctint-12.c', + 'wrapper/jidctint-8.c', + 'wrapper/jidctred-12.c', + 'wrapper/jidctred-8.c', + 'wrapper/jquant1-12.c', + 'wrapper/jquant1-8.c', + 'wrapper/jquant2-12.c', + 'wrapper/jquant2-8.c', + 'wrapper/jutils-12.c', + 'wrapper/jutils-16.c', + 'wrapper/jutils-8.c', +) + +sources += files( + # TODO: `with_arith_dec` / `with_arith_enc` only. + 'jaricom.c', + # TODO: `with_arith_enc` only + 'jcarith.c', + # TODO: `with_arith_dec` only + 'jdarith.c', +) + +jpeg = library( + 'jpeg', + sources, + link_whole : simd, + soversion : so_version, + vs_module_defs : vs_defs, + install : true, +) + +pkg.generate( + jpeg, + description : 'A SIMD-accelerated JPEG codec that provides the libjpeg API', + name : 'libjpeg', +) + +jpeg_dep = declare_dependency( + include_directories : incdir, + link_with : jpeg, +) +meson.override_dependency('libjpeg', jpeg_dep) + +if get_option('turbojpeg').allowed() + install_headers('turbojpeg.h') + + turbojpeg = library( + 'turbojpeg', + sources, + files( + 'jdatadst-tj.c', + 'jdatasrc-tj.c', + 'rdbmp.c', + 'transupp.c', + 'turbojpeg.c', + 'wrapper/rdppm-12.c', + 'wrapper/rdppm-16.c', + 'wrapper/rdppm-8.c', + 'wrapper/wrppm-12.c', + 'wrapper/wrppm-16.c', + 'wrapper/wrppm-8.c', + 'wrbmp.c', + ), + c_args : ['-DBMP_SUPPORTED', '-DPPM_SUPPORTED'], + install : true, + link_whole : simd, + soversion : '0.4.0', + ) + + pkg.generate( + turbojpeg, + description : 'A SIMD-accelerated JPEG codec that provides the TurboJPEG API', + name : 'libturbojpeg', + ) + + turbojpeg_dep = declare_dependency( + include_directories : incdir, + link_with : turbojpeg, + ) + meson.override_dependency('libturbojpeg', turbojpeg_dep) +endif + +if get_option('tests').require( + get_option('turbojpeg').allowed(), + error_message : 'turbojpeg feature needed', +).allowed() + tjunittest = executable( + 'tjunittest', + ['tjunittest.c', 'tjutil.c', 'md5/md5.c', 'md5/md5hl.c'], + dependencies : turbojpeg_dep, + ) + + foreach _test, _args : { + 'tjunittest' : '', + 'tjunittest-alloc' : '-alloc', + 'tjunittest-yuv' : '-yuv', + 'tjunittest-yuv-alloc' : '-yuv -alloc', + 'tjunittest-yuv-nopad' : '-yuv -noyuvpad', + 'tjunittest-lossless' : '-lossless', + 'tjunittest-lossless-alloc' : '-lossless -alloc', + 'tjunittest-bmp' : '-bmp', + 'tjunittest12' : '-precision 12', + 'tjunittest12-alloc' : '-precision 12 -alloc', + 'tjunittest12-lossless' : '-precision 12 -lossless', + 'tjunittest12-lossless-alloc' : '-precision 12 -lossless -alloc', + 'tjunittest12-bmp' : '-precision 12 -bmp', + 'tjunittest2-lossless' : '-precision 2', + 'tjunittest2-lossless-alloc' : '-precision 2 -alloc', + 'tjunittest2-bmp' : '-precision 2 -bmp', + 'tjunittest3-lossless' : '-precision 3', + 'tjunittest3-lossless-alloc' : '-precision 3 -alloc', + 'tjunittest3-bmp' : '-precision 3 -bmp', + 'tjunittest4-lossless' : '-precision 4', + 'tjunittest4-lossless-alloc' : '-precision 4 -alloc', + 'tjunittest4-bmp' : '-precision 4 -bmp', + 'tjunittest5-lossless' : '-precision 5', + 'tjunittest5-lossless-alloc' : '-precision 5 -alloc', + 'tjunittest5-bmp' : '-precision 5 -bmp', + 'tjunittest6-lossless' : '-precision 6', + 'tjunittest6-lossless-alloc' : '-precision 6 -alloc', + 'tjunittest6-bmp' : '-precision 6 -bmp', + 'tjunittest7-lossless' : '-precision 7', + 'tjunittest7-lossless-alloc' : '-precision 7 -alloc', + 'tjunittest7-bmp' : '-precision 7 -bmp', + 'tjunittest9-lossless' : '-precision 9', + 'tjunittest9-lossless-alloc' : '-precision 9 -alloc', + 'tjunittest9-bmp' : '-precision 9 -bmp', + 'tjunittest10-lossless' : '-precision 10', + 'tjunittest10-lossless-alloc' : '-precision 10 -alloc', + 'tjunittest10-bmp' : '-precision 10 -bmp', + 'tjunittest11-lossless' : '-precision 11', + 'tjunittest11-lossless-alloc' : '-precision 11 -alloc', + 'tjunittest11-bmp' : '-precision 11 -bmp', + 'tjunittest13-lossless' : '-precision 13', + 'tjunittest13-lossless-alloc' : '-precision 13 -alloc', + 'tjunittest13-bmp' : '-precision 13 -bmp', + 'tjunittest14-lossless' : '-precision 14', + 'tjunittest14-lossless-alloc' : '-precision 14 -alloc', + 'tjunittest14-bmp' : '-precision 14 -bmp', + 'tjunittest15-lossless' : '-precision 15', + 'tjunittest15-lossless-alloc' : '-precision 15 -alloc', + 'tjunittest15-bmp' : '-precision 15 -bmp', + 'tjunittest16-lossless' : '-precision 16', + 'tjunittest16-lossless-alloc' : '-precision 16 -alloc', + 'tjunittest16-bmp' : '-precision 16 -bmp', + } + test( + _test, + tjunittest, + args : _args.split(), + timeout : 120, + ) + endforeach +endif diff --git a/native/subprojects.tpl/linux/packagefiles/mpv/subprojects/zlib.wrap b/native/subprojects.tpl/linux/packagefiles/mpv/subprojects/zlib.wrap new file mode 100644 index 0000000..a581b1e --- /dev/null +++ b/native/subprojects.tpl/linux/packagefiles/mpv/subprojects/zlib.wrap @@ -0,0 +1,6 @@ +[wrap-file] +source_filename = zlib.tar +lead_directory_missing = true + +[provide] +zlib = zlib_dep diff --git a/native/subprojects.tpl/windows/packagefiles/mpv/meson.build b/native/subprojects.tpl/windows/packagefiles/mpv/meson.build new file mode 100644 index 0000000..7c12e24 --- /dev/null +++ b/native/subprojects.tpl/windows/packagefiles/mpv/meson.build @@ -0,0 +1,13 @@ +project( + 'mpv', 'c', + meson_version : '>=1.2.0', +) + +cc = meson.get_compiler('c') + +libmpv_dep = declare_dependency( + version : '2', + dependencies : cc.find_library('mpv', dirs : meson.current_source_dir()), + include_directories : include_directories('include'), +) +meson.override_dependency('libmpv', libmpv_dep) diff --git a/native/subprojects.tpl/windows/packagefiles/mpv/meson.options b/native/subprojects.tpl/windows/packagefiles/mpv/meson.options new file mode 100644 index 0000000..811b382 --- /dev/null +++ b/native/subprojects.tpl/windows/packagefiles/mpv/meson.options @@ -0,0 +1,3 @@ +option('libmpv', type: 'boolean', value: true) +option('drm', type: 'feature', value: 'auto') +option('openal', type: 'feature', value: 'auto') diff --git a/native/thirdparty/ffmpeg.cmake b/native/thirdparty/ffmpeg.cmake deleted file mode 100644 index cc8da95..0000000 --- a/native/thirdparty/ffmpeg.cmake +++ /dev/null @@ -1,71 +0,0 @@ -set(FFMPEG_URL "https://reposilite.silenium.dev/releases/dev/silenium/libs/ffmpeg/ffmpeg-natives-${NATIVE_PLATFORM}${FFMPEG_PLATFORM_EXTENSION}/${FFMPEG_VERSION}/ffmpeg-natives-${NATIVE_PLATFORM}${FFMPEG_PLATFORM_EXTENSION}-${FFMPEG_VERSION}.zip") -set(FFMPEG_URL_SHA256 "https://reposilite.silenium.dev/releases/dev/silenium/libs/ffmpeg/ffmpeg-natives-${NATIVE_PLATFORM}${FFMPEG_PLATFORM_EXTENSION}/${FFMPEG_VERSION}/ffmpeg-natives-${NATIVE_PLATFORM}${FFMPEG_PLATFORM_EXTENSION}-${FFMPEG_VERSION}.zip.sha256") -set(FFMPEG_PREFIX "${CMAKE_BINARY_DIR}/ffmpeg") -message(STATUS "Downloading ffmpeg from ${FFMPEG_URL}") - -file(DOWNLOAD "${FFMPEG_URL_SHA256}" "${CMAKE_BINARY_DIR}/ffmpeg.zip.sha256") -file(READ "${CMAKE_BINARY_DIR}/ffmpeg.zip.sha256" FFMPEG_SHA256) -file(DOWNLOAD "${FFMPEG_URL}" "${CMAKE_BINARY_DIR}/ffmpeg.zip" EXPECTED_HASH SHA256=${FFMPEG_SHA256} SHOW_PROGRESS) -file(ARCHIVE_EXTRACT INPUT "${CMAKE_BINARY_DIR}/ffmpeg.zip" DESTINATION "${FFMPEG_PREFIX}") - -set(FFMPEG_INCLUDE_DIR "${FFMPEG_PREFIX}/include") -set(FFMPEG_LIB_DIR "${FFMPEG_PREFIX}/lib") -set(FFMPEG_LIBRARIES - aom - crypto - freetype - mp3lame - opencore-amrnb - opencore-amrwb - openh264 - opus - sharpyuv - speex - srt - ssl - SvtAv1Dec - SvtAv1Enc - vo-amrwbenc - vpx - webp - webpdemux - webpmux - x264 - x265 - xml2 - z - zimg - avcodec - avdevice - avfilter - avformat - avutil - postproc - swresample - swscale) -add_library(ffmpeg STATIC IMPORTED) - -set(FFMPEG_MRI "${CMAKE_CURRENT_BINARY_DIR}/ffmpeg.mri") -file(WRITE "${FFMPEG_MRI}" "CREATE libffmpeg.a\n") -message(STATUS "Checking for ffmpeg libraries") -foreach (FFMPEG_LIBRARY ${FFMPEG_LIBRARIES}) - set(LIB_PATH "${FFMPEG_LIB_DIR}/lib${FFMPEG_LIBRARY}.a") - if (NOT EXISTS ${LIB_PATH}) - message(STATUS " ${FFMPEG_LIBRARY} not found") - continue() - endif () - message(STATUS " Found ${FFMPEG_LIBRARY}") - file(APPEND "${FFMPEG_MRI}" "ADDLIB ${LIB_PATH}\n") -endforeach () -file(APPEND "${CMAKE_CURRENT_BINARY_DIR}/ffmpeg.mri" "SAVE\nEND\n") - -add_custom_target(ffmpeg_custom - COMMAND ar -M < "${CMAKE_CURRENT_BINARY_DIR}/ffmpeg.mri" - WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR} - BYPRODUCTS ${CMAKE_CURRENT_BINARY_DIR}/libffmpeg.a -) -add_dependencies(ffmpeg ffmpeg_custom) -set_target_properties(ffmpeg PROPERTIES IMPORTED_LOCATION "${CMAKE_CURRENT_BINARY_DIR}/libffmpeg.a") - -target_include_directories(ffmpeg INTERFACE "${FFMPEG_INCLUDE_DIR}") -target_link_options(ffmpeg INTERFACE "-Wl,-Bsymbolic") diff --git a/native/thirdparty/mpv.cmake b/native/thirdparty/mpv.cmake deleted file mode 100644 index 53612b2..0000000 --- a/native/thirdparty/mpv.cmake +++ /dev/null @@ -1,46 +0,0 @@ -set(MPV_URL "https://reposilite.silenium.dev/releases/dev/silenium/libs/mpv/mpv-natives-${NATIVE_PLATFORM}/${MPV_VERSION}/mpv-natives-${NATIVE_PLATFORM}-${MPV_VERSION}.zip") -set(MPV_URL_SHA256 "https://reposilite.silenium.dev/releases/dev/silenium/libs/mpv/mpv-natives-${NATIVE_PLATFORM}/${MPV_VERSION}/mpv-natives-${NATIVE_PLATFORM}-${MPV_VERSION}.zip.sha256") -set(MPV_PREFIX "${CMAKE_BINARY_DIR}/mpv") -message(STATUS "Downloading mpv from ${MPV_URL}") - -file(DOWNLOAD "${MPV_URL_SHA256}" "${CMAKE_BINARY_DIR}/mpv.zip.sha256") -file(READ "${CMAKE_BINARY_DIR}/mpv.zip.sha256" MPV_SHA256) -file(DOWNLOAD "${MPV_URL}" "${CMAKE_BINARY_DIR}/mpv.zip" EXPECTED_HASH SHA256=${MPV_SHA256} SHOW_PROGRESS) -file(ARCHIVE_EXTRACT INPUT "${CMAKE_BINARY_DIR}/mpv.zip" DESTINATION "${MPV_PREFIX}") - -set(MPV_INCLUDE_DIR "${MPV_PREFIX}/include") -set(MPV_LIB_DIR "${MPV_PREFIX}/lib") -set(MPV_LIBRARIES - mpv - ass - placebo) -add_library(mpv STATIC IMPORTED) - -set(MPV_MRI "${CMAKE_CURRENT_BINARY_DIR}/mpv.mri") -file(WRITE "${MPV_MRI}" "CREATE libmpv.a\n") -message(STATUS "Checking for mpv libraries") -foreach (MPV_LIBRARY ${MPV_LIBRARIES}) - set(LIB_PATH "${MPV_LIB_DIR}/lib${MPV_LIBRARY}.a") - if (NOT EXISTS ${LIB_PATH}) - message(STATUS " ${MPV_LIBRARY} not found") - continue() - endif () - message(STATUS " Found ${MPV_LIBRARY}") - file(APPEND "${MPV_MRI}" "ADDLIB ${LIB_PATH}\n") -endforeach () -file(APPEND "${CMAKE_CURRENT_BINARY_DIR}/mpv.mri" "SAVE\nEND\n") - -add_custom_target(mpv_custom - COMMAND ar -M < "${CMAKE_CURRENT_BINARY_DIR}/mpv.mri" - WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR} - BYPRODUCTS ${CMAKE_CURRENT_BINARY_DIR}/libmpv.a -) -add_dependencies(mpv mpv_custom) -set_target_properties(mpv PROPERTIES IMPORTED_LOCATION "${CMAKE_CURRENT_BINARY_DIR}/libmpv.a") - -target_include_directories(mpv INTERFACE "${MPV_INCLUDE_DIR}") -target_link_options(mpv INTERFACE "-Wl,-Bsymbolic") - -find_package(PkgConfig REQUIRED) -pkg_check_modules(MPV_deps REQUIRED IMPORTED_TARGET libva libva-drm libdrm libva-glx libva-x11 libpipewire-0.3) -target_link_libraries(mpv INTERFACE PkgConfig::MPV_deps) diff --git a/settings.gradle.kts b/settings.gradle.kts index d7fceb0..43c8ba3 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -1,12 +1,25 @@ +pluginManagement { + repositories { + maven("https://reposilite.silenium.dev/releases") { + name = "silenium-releases" + } + google() + mavenCentral() + gradlePluginPortal() + } +} + plugins { id("org.gradle.toolchains.foojay-resolver-convention") version "0.9.0" } -val deployNative = if (extra.has("deploy.native")) { - extra.get("deploy.native")?.toString()?.toBoolean() ?: true +val deployKotlin = if (extra.has("deploy.kotlin")) { + extra.get("deploy.kotlin")?.toString()?.toBoolean() ?: true } else true -if (deployNative) { +if (deployKotlin) { include(":native") } rootProject.name = "compose-av" + +includeBuild("build-logic") diff --git a/src/main/kotlin/dev/silenium/multimedia/compose/player/VideoPlayer.kt b/src/main/kotlin/dev/silenium/multimedia/compose/player/VideoPlayer.kt index 1d7dcc5..1e1cec3 100644 --- a/src/main/kotlin/dev/silenium/multimedia/compose/player/VideoPlayer.kt +++ b/src/main/kotlin/dev/silenium/multimedia/compose/player/VideoPlayer.kt @@ -1,9 +1,8 @@ package dev.silenium.multimedia.compose.player import androidx.compose.runtime.* -import dev.silenium.compose.gl.surface.GLDrawScope -import dev.silenium.compose.gl.surface.GLSurface -import dev.silenium.compose.gl.surface.GLSurfaceState +import dev.silenium.compose.gl.canvas.GLCanvasState +import dev.silenium.compose.gl.canvas.GLDrawScope import dev.silenium.multimedia.compose.util.deferredFlowStateOf import dev.silenium.multimedia.compose.util.mapState import dev.silenium.multimedia.core.annotation.InternalMultimediaApi @@ -12,6 +11,7 @@ import org.lwjgl.opengl.GL30.* import kotlin.time.Duration import kotlin.time.Duration.Companion.seconds +@Stable class VideoPlayer(hwdec: Boolean = false) : AutoCloseable { class Config(pixelPerfect: Boolean = false) { var pixelPerfect by mutableStateOf(pixelPerfect) @@ -19,7 +19,6 @@ class VideoPlayer(hwdec: Boolean = false) : AutoCloseable { val config: Config = Config() - internal var surface: GLSurface? = null private var initialized = false @PublishedApi @@ -68,21 +67,21 @@ class VideoPlayer(hwdec: Boolean = false) : AutoCloseable { return mpv } - private fun initialize(state: GLSurfaceState) { + private fun initialize(state: GLCanvasState, onInitialized: () -> Unit) { if (initialized) return render = mpv.createRender(advancedControl = true, state::requestUpdate) initialized = true + onInitialized() } - fun onRender(scope: GLDrawScope, state: GLSurfaceState) { - initialize(state) + fun onRender(scope: GLDrawScope, state: GLCanvasState, onInitialized: () -> Unit = {}) { + initialize(state, onInitialized) // TODO: fix render block if screen is disconnected and reconnected glClearColor(0f, 0f, 0f, 0f) glClear(GL_COLOR_BUFFER_BIT) render?.render(scope.fbo)?.getOrThrow() - scope.redrawAfter(null) } override fun close() { @@ -90,6 +89,11 @@ class VideoPlayer(hwdec: Boolean = false) : AutoCloseable { mpv.close() } + fun onCanvasDispose() { + render?.close() + render = null + } + companion object { private val defaultOptions = mapOf( "terminal" to "yes", diff --git a/src/main/kotlin/dev/silenium/multimedia/compose/player/VideoPlayerControls.kt b/src/main/kotlin/dev/silenium/multimedia/compose/player/VideoPlayerControls.kt index cf71d97..9ad74c3 100644 --- a/src/main/kotlin/dev/silenium/multimedia/compose/player/VideoPlayerControls.kt +++ b/src/main/kotlin/dev/silenium/multimedia/compose/player/VideoPlayerControls.kt @@ -4,7 +4,6 @@ import androidx.compose.animation.AnimatedContent import androidx.compose.animation.ContentTransform import androidx.compose.animation.fadeIn import androidx.compose.animation.fadeOut -import androidx.compose.desktop.ui.tooling.preview.Preview import androidx.compose.foundation.clickable import androidx.compose.foundation.focusable import androidx.compose.foundation.interaction.MutableInteractionSource @@ -29,7 +28,6 @@ import kotlinx.coroutines.launch import kotlin.time.Duration.Companion.seconds @OptIn(InternalMultimediaApi::class) -@Preview @Composable fun VideoSurfaceControls( player: VideoPlayer, @@ -115,7 +113,7 @@ fun VideoSurfaceControls( ) { AnimatedContent(paused, transitionSpec = { ContentTransform(fadeIn(), fadeOut()) - }) { it -> + }) { if (it != false) { Icon( Icons.Default.PlayArrow, @@ -177,7 +175,7 @@ fun VideoSurfaceControls( ) { AnimatedContent(fullscreenProvider.isFullscreen, transitionSpec = { ContentTransform(fadeIn(), fadeOut()) - }) { it -> + }) { if (it) { Icon( Icons.Default.FullscreenExit, diff --git a/src/main/kotlin/dev/silenium/multimedia/compose/player/VideoSurface.kt b/src/main/kotlin/dev/silenium/multimedia/compose/player/VideoSurface.kt index 428f50f..6a0071b 100644 --- a/src/main/kotlin/dev/silenium/multimedia/compose/player/VideoSurface.kt +++ b/src/main/kotlin/dev/silenium/multimedia/compose/player/VideoSurface.kt @@ -5,49 +5,50 @@ import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.width import androidx.compose.material.MaterialTheme import androidx.compose.material.Surface -import androidx.compose.runtime.* +import androidx.compose.runtime.Composable +import androidx.compose.runtime.remember import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Color import androidx.compose.ui.unit.dp -import dev.silenium.compose.gl.surface.* -import dev.silenium.multimedia.core.annotation.InternalMultimediaApi +import dev.silenium.compose.gl.canvas.GLCanvas +import dev.silenium.compose.gl.canvas.rememberGLCanvasState import org.jetbrains.skia.Paint -@Composable -private fun createGLSurface( - player: VideoPlayer, - surfaceState: GLSurfaceState = rememberGLSurfaceState(), - onInitialized: () -> Unit = {}, -): GLSurface { - var initialized by remember { mutableStateOf(false) } - - @OptIn(InternalMultimediaApi::class) - val dwidth by player.property("dwidth") - - @OptIn(InternalMultimediaApi::class) - val dheight by player.property("dheight") - val fboSizeOverride = remember(player.config.pixelPerfect, dwidth, dheight) { - if (!player.config.pixelPerfect) return@remember null - dwidth?.let { w -> - dheight?.let { h -> - FBOSizeOverride(w.toInt(), h.toInt()) - } - } - } - return rememberGLSurface( - surfaceState, - presentMode = GLSurface.PresentMode.MAILBOX, - swapChainSize = 3, - fboSizeOverride = fboSizeOverride, - draw = { - player.onRender(this, surfaceState) - if (!initialized) { - initialized = true - onInitialized() - } - } - ) -} +//@Composable +//private fun createGLSurface( +// player: VideoPlayer, +// surfaceState: GLSurfaceState = rememberGLSurfaceState(), +// onInitialized: () -> Unit = {}, +//): GLSurface { +// var initialized by remember { mutableStateOf(false) } +// +// @OptIn(InternalMultimediaApi::class) +// val dwidth by player.property("dwidth") +// +// @OptIn(InternalMultimediaApi::class) +// val dheight by player.property("dheight") +// val fboSizeOverride = remember(player.config.pixelPerfect, dwidth, dheight) { +// if (!player.config.pixelPerfect) return@remember null +// dwidth?.let { w -> +// dheight?.let { h -> +// FBOSizeOverride(w.toInt(), h.toInt()) +// } +// } +// } +// return rememberGLSurface( +// surfaceState, +// presentMode = GLSurface.PresentMode.MAILBOX, +// swapChainSize = 3, +// fboSizeOverride = fboSizeOverride, +// draw = { +// player.onRender(this, surfaceState) +// if (!initialized) { +// initialized = true +// onInitialized() +// } +// } +// ) +//} @Composable fun VideoSurface( @@ -55,14 +56,17 @@ fun VideoSurface( showStats: Boolean = false, modifier: Modifier = Modifier, paint: Paint = Paint(), + onInitialized: () -> Unit = {}, ) { - val surfaceState = rememberGLSurfaceState() + val surfaceState = rememberGLCanvasState() BoxWithConstraints(modifier = modifier) { - GLSurfaceView( - surface = player.surface!!, + GLCanvas( modifier = Modifier.matchParentSize(), - paint = paint, - ) + state = surfaceState, +// onDispose = player::onCanvasDispose, + ) { + player.onRender(this, surfaceState, onInitialized) + } if (showStats) { Surface( modifier = Modifier.padding(6.dp).width(360.dp), @@ -77,19 +81,17 @@ fun VideoSurface( @Composable fun rememberVideoPlayer( - surfaceState: GLSurfaceState = rememberGLSurfaceState(), hwdec: Boolean = true, - onInitialized: () -> Unit = {}, ): VideoPlayer { val player = remember { VideoPlayer(hwdec) } - val surface = createGLSurface(player, surfaceState, onInitialized) +// val surface = createGLSurface(player, surfaceState, onInitialized) - DisposableEffect(player, surface) { - player.surface = surface - onDispose { - player.surface = null - player.close() - } - } +// DisposableEffect(player, surface) { +// player.surface = surface +// onDispose { +// player.surface = null +// player.close() +// } +// } return player } diff --git a/src/main/kotlin/dev/silenium/multimedia/compose/player/VideoSurfaceEvents.kt b/src/main/kotlin/dev/silenium/multimedia/compose/player/VideoSurfaceEvents.kt index bf10d46..d385653 100644 --- a/src/main/kotlin/dev/silenium/multimedia/compose/player/VideoSurfaceEvents.kt +++ b/src/main/kotlin/dev/silenium/multimedia/compose/player/VideoSurfaceEvents.kt @@ -13,11 +13,12 @@ import dev.silenium.multimedia.core.annotation.InternalMultimediaApi import kotlinx.coroutines.Job import kotlinx.coroutines.delay import kotlinx.coroutines.launch -import kotlinx.datetime.Clock -import kotlinx.datetime.Instant +import kotlin.time.Clock import kotlin.time.Duration.Companion.milliseconds +import kotlin.time.ExperimentalTime +import kotlin.time.Instant -@OptIn(ExperimentalComposeUiApi::class, InternalMultimediaApi::class) +@OptIn(ExperimentalComposeUiApi::class, InternalMultimediaApi::class, ExperimentalTime::class) @Composable fun Modifier.handleInputs(player: VideoPlayer, focusRequester: FocusRequester? = null): Modifier { val coroutineScope = rememberCoroutineScope() diff --git a/src/main/kotlin/dev/silenium/multimedia/compose/player/VideoSurfaceStats.kt b/src/main/kotlin/dev/silenium/multimedia/compose/player/VideoSurfaceStats.kt index 57990eb..523cd5c 100644 --- a/src/main/kotlin/dev/silenium/multimedia/compose/player/VideoSurfaceStats.kt +++ b/src/main/kotlin/dev/silenium/multimedia/compose/player/VideoSurfaceStats.kt @@ -9,13 +9,13 @@ import androidx.compose.runtime.getValue import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Color import androidx.compose.ui.unit.dp -import dev.silenium.compose.gl.surface.GLSurfaceState -import dev.silenium.compose.gl.surface.Stats +import dev.silenium.compose.gl.canvas.GLCanvasState +import dev.silenium.compose.gl.canvas.Stats import dev.silenium.multimedia.compose.format.format import dev.silenium.multimedia.compose.util.deferredFlowStateOf @Composable -fun VideoSurfaceStats(player: VideoPlayer, state: GLSurfaceState, textColor: Color = Color.White) { +fun VideoSurfaceStats(player: VideoPlayer, state: GLCanvasState, textColor: Color = Color.White) { Column(modifier = Modifier.padding(6.dp)) { val position by deferredFlowStateOf(player::position) val duration by deferredFlowStateOf(player::duration) diff --git a/src/main/kotlin/dev/silenium/multimedia/compose/player/VideoSurfaceWithControls.kt b/src/main/kotlin/dev/silenium/multimedia/compose/player/VideoSurfaceWithControls.kt index f237708..4dbb176 100644 --- a/src/main/kotlin/dev/silenium/multimedia/compose/player/VideoSurfaceWithControls.kt +++ b/src/main/kotlin/dev/silenium/multimedia/compose/player/VideoSurfaceWithControls.kt @@ -15,6 +15,7 @@ fun VideoSurfaceWithControls( showStats: Boolean = false, controlFocusRequester: FocusRequester? = null, paint: Paint = Paint(), + onInitialized: () -> Unit = {}, ) { BoxWithConstraints(modifier) { VideoSurface( @@ -26,6 +27,7 @@ fun VideoSurfaceWithControls( maxHeight = maxHeight, ), paint = paint, + onInitialized = onInitialized, ) VideoSurfaceControls(player, Modifier.matchParentSize(), controlFocusRequester) } diff --git a/src/main/kotlin/dev/silenium/multimedia/core/mpv/MPV.kt b/src/main/kotlin/dev/silenium/multimedia/core/mpv/MPV.kt index 055c1aa..de6b5b6 100644 --- a/src/main/kotlin/dev/silenium/multimedia/core/mpv/MPV.kt +++ b/src/main/kotlin/dev/silenium/multimedia/core/mpv/MPV.kt @@ -400,6 +400,7 @@ class MPV : NativeCleanable, MPVAsyncListener { } override fun close() { + callback?.let(::unsetCallbackN) initialized.set(false) super.close() } diff --git a/src/main/kotlin/dev/silenium/multimedia/core/util/Errors.kt b/src/main/kotlin/dev/silenium/multimedia/core/util/Errors.kt index 9b328db..ae16746 100644 --- a/src/main/kotlin/dev/silenium/multimedia/core/util/Errors.kt +++ b/src/main/kotlin/dev/silenium/multimedia/core/util/Errors.kt @@ -1,8 +1,5 @@ package dev.silenium.multimedia.core.util -class AVException(val operation: String, val error: Int) : - Exception("FFmpeg error during $operation: ${error.asAVErrorString()}") - class EGLException(val operation: String, val error: Long) : Exception("EGL error during $operation: ${error.asEGLErrorString()}") @@ -15,31 +12,19 @@ class VAException(val operation: String, val error: Int) : class MPVException(val operation: String, val error: Int) : Exception("MPV error during $operation: ${error.asMPVErrorString()}") -fun Int.asAVError(operation: String = "ffmpeg call"): Exception = AVException(operation, this) -fun Int.asAVErrorString(): String = avErrorStringN(this) fun Long.asEGLError(operation: String = "EGL call"): Exception = EGLException(operation, this) fun Long.asEGLErrorString(): String = eglErrorStringN(this) + fun Int.asGLError(operation: String = "GL call"): Exception = GLException(operation, this) fun Int.asGLErrorString(): String = glErrorStringN(this) + fun Int.asVAError(operation: String = "VA call"): Exception = VAException(operation, this) fun Int.asVAErrorString(): String = vaErrorStringN(this) + fun Int.asMPVError(operation: String = "MPV call"): Exception = MPVException(operation, this) fun Int.asMPVErrorString(): String = mpvErrorStringN(this) -private external fun avErrorStringN(error: Int): String -private external fun eglErrorStringN(error: Long): String +private external fun mpvErrorStringN(error: Int): String private external fun glErrorStringN(error: Int): String +private external fun eglErrorStringN(error: Long): String private external fun vaErrorStringN(error: Int): String -private external fun mpvErrorStringN(error: Int): String - -val Throwable.shouldIgnore - get() = when (this) { - is AVException -> when (error) { - -11 -> true // EAGAIN - -12 -> true // ENOMEM - -541478725 -> true // AVERROR_EOF - else -> false - } - - else -> false - } diff --git a/src/main/kotlin/dev/silenium/multimedia/core/util/Natives.kt b/src/main/kotlin/dev/silenium/multimedia/core/util/Natives.kt index b4a5cce..44e748d 100644 --- a/src/main/kotlin/dev/silenium/multimedia/core/util/Natives.kt +++ b/src/main/kotlin/dev/silenium/multimedia/core/util/Natives.kt @@ -1,15 +1,38 @@ package dev.silenium.multimedia.core.util import dev.silenium.libs.jni.NativeLoader +import dev.silenium.libs.jni.Platform import dev.silenium.multimedia.build.BuildConstants object Natives { private var loaded = false + private val platformDeps = mapOf( + Platform.OS.LINUX to setOf( + "avcodec", + "avdevice", + "avfilter", + "avformat", + "avutil", + "compose-av", + "swresample", + "swscale", + ), + Platform.OS.WINDOWS to setOf( + "compose-av", + "libmpv-2", + ), + ) + @Synchronized fun ensureLoaded() { if (!loaded) { + val deps = platformDeps[NativeLoader.nativePlatform.os] + ?: error("Unsupported platform: ${NativeLoader.nativePlatform}") + deps.forEach { + NativeLoader.loadLibraryFromClasspath(it).getOrThrow() + } NativeLoader.loadLibraryFromClasspath(BuildConstants.LIBRARY_NAME).getOrThrow() loaded = true } diff --git a/src/main/kotlin/dev/silenium/multimedia/core/util/Results.kt b/src/main/kotlin/dev/silenium/multimedia/core/util/Results.kt deleted file mode 100644 index 6ed07d0..0000000 --- a/src/main/kotlin/dev/silenium/multimedia/core/util/Results.kt +++ /dev/null @@ -1,7 +0,0 @@ -package dev.silenium.multimedia.core.util - -fun Int.asUnitResult() = if (this != 0) { - Result.failure(this.asAVError()) -} else { - Result.success(Unit) -} diff --git a/src/test/kotlin/dev/silenium/multimedia/compose/Main.kt b/src/test/kotlin/dev/silenium/multimedia/compose/Main.kt index e20ec19..39296b0 100644 --- a/src/test/kotlin/dev/silenium/multimedia/compose/Main.kt +++ b/src/test/kotlin/dev/silenium/multimedia/compose/Main.kt @@ -15,7 +15,7 @@ import androidx.compose.ui.Modifier import androidx.compose.ui.focus.FocusRequester import androidx.compose.ui.unit.dp import androidx.compose.ui.window.Window -import androidx.compose.ui.window.awaitApplication +import androidx.compose.ui.window.application import dev.silenium.multimedia.compose.player.VideoSurfaceWithControls import dev.silenium.multimedia.compose.player.rememberVideoPlayer import dev.silenium.multimedia.compose.util.LocalFullscreenProvider @@ -25,7 +25,6 @@ import java.nio.file.Files import kotlin.io.path.absolutePathString import kotlin.io.path.outputStream import kotlin.time.Duration.Companion.milliseconds -import kotlin.time.Duration.Companion.seconds @OptIn(InternalMultimediaApi::class) @Composable @@ -40,11 +39,7 @@ fun App() { } var ready by remember { mutableStateOf(false) } val coroutineScope = rememberCoroutineScope() - val player = rememberVideoPlayer( - onInitialized = { - ready = true - }, - ) + val player = rememberVideoPlayer() DisposableEffect(Unit) { onDispose { ready = false @@ -63,12 +58,12 @@ fun App() { ) { var visible by remember { mutableStateOf(true) } var wasPaused by remember { mutableStateOf("no") } - LaunchedEffect(Unit) { - while (isActive) { - delay(2.seconds) - visible = !visible - } - } +// LaunchedEffect(Unit) { +// while (isActive) { +// delay(2.seconds) +// visible = !visible +// } +// } val modifier = when { fullscreen -> Modifier.size( this@BoxWithConstraints.maxWidth, @@ -94,6 +89,9 @@ fun App() { modifier = Modifier.fillParentMaxSize().animateItem(), showStats = true, controlFocusRequester = remember { FocusRequester() }, + onInitialized = { + ready = true + }, ) DisposableEffect(Unit) { coroutineScope.launch { @@ -147,7 +145,7 @@ fun App() { } } -suspend fun main(): Unit = awaitApplication { +fun main(): Unit = application { val state = LocalFullscreenProvider.current.windowState Window(state = state, onCloseRequest = ::exitApplication) { App() diff --git a/src/test/kotlin/dev/silenium/multimedia/simple/Main.kt b/src/test/kotlin/dev/silenium/multimedia/simple/Main.kt index 0b02a94..4594a7e 100644 --- a/src/test/kotlin/dev/silenium/multimedia/simple/Main.kt +++ b/src/test/kotlin/dev/silenium/multimedia/simple/Main.kt @@ -1,16 +1,14 @@ package dev.silenium.multimedia.simple import androidx.compose.foundation.layout.fillMaxSize -import androidx.compose.foundation.pager.VerticalPager -import androidx.compose.foundation.pager.rememberPagerState import androidx.compose.runtime.remember import androidx.compose.ui.Modifier import androidx.compose.ui.window.Window -import androidx.compose.ui.window.awaitApplication +import androidx.compose.ui.window.application import java.nio.file.Files import kotlin.io.path.outputStream -suspend fun main() = awaitApplication { +fun main() = application { val file = remember { val videoFile = Files.createTempFile("video", ".webm") Thread.currentThread().contextClassLoader.getResourceAsStream("1080p.webm").use { @@ -20,9 +18,6 @@ suspend fun main() = awaitApplication { } Window(onCloseRequest = this::exitApplication) { - val state = rememberPagerState { 1000 } - VerticalPager(state = state, modifier = Modifier.fillMaxSize(), beyondViewportPageCount = 2) { - VideoPlayer(file = file, suspend = state.currentPage != it, modifier = Modifier.fillMaxSize()) - } + VideoPlayer(file = file, modifier = Modifier.fillMaxSize()) } } diff --git a/src/test/kotlin/dev/silenium/multimedia/simple/VideoPlayer.kt b/src/test/kotlin/dev/silenium/multimedia/simple/VideoPlayer.kt index 272feb3..1490934 100644 --- a/src/test/kotlin/dev/silenium/multimedia/simple/VideoPlayer.kt +++ b/src/test/kotlin/dev/silenium/multimedia/simple/VideoPlayer.kt @@ -2,9 +2,8 @@ package dev.silenium.multimedia.simple import androidx.compose.runtime.* import androidx.compose.ui.Modifier -import dev.silenium.compose.gl.surface.GLSurface -import dev.silenium.compose.gl.surface.GLSurfaceView -import dev.silenium.compose.gl.surface.rememberGLSurfaceState +import dev.silenium.compose.gl.canvas.GLCanvas +import dev.silenium.compose.gl.canvas.rememberGLCanvasState import dev.silenium.multimedia.core.mpv.MPV import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.delay @@ -42,8 +41,8 @@ fun VideoPlayer(file: Path, suspend: Boolean = false, modifier: Modifier = Modif mpv.commandAsync("set", "pause", if (suspend) "yes" else "no").getOrThrow() } } - val state = rememberGLSurfaceState() - GLSurfaceView(state, modifier = modifier, presentMode = GLSurface.PresentMode.MAILBOX, swapChainSize = 3) { + val state = rememberGLCanvasState() + GLCanvas(state, modifier = modifier) { if (!ready) { render = mpv.createRender(advancedControl = true, state::requestUpdate) ready = true