diff --git a/common/.gitignore b/common/.gitignore new file mode 100644 index 0000000..42afabf --- /dev/null +++ b/common/.gitignore @@ -0,0 +1 @@ +/build \ No newline at end of file diff --git a/common/build.gradle.kts b/common/build.gradle.kts new file mode 100644 index 0000000..2676051 --- /dev/null +++ b/common/build.gradle.kts @@ -0,0 +1,98 @@ +import org.jetbrains.kotlin.gradle.dsl.JvmTarget +import org.jetbrains.kotlin.gradle.dsl.KotlinVersion + +buildscript { + dependencies { + classpath(libs.dokka.base) + } +} + +plugins { + alias(libs.plugins.android.library) + alias(libs.plugins.kotlin.android) + alias(libs.plugins.compose.compiler) + alias(libs.plugins.dokka) + alias(libs.plugins.dokka.javadoc) + id("android-ui.library-conventions") +} + +android { + namespace = "com.theoplayer.android.ui.common" + compileSdk = libs.versions.android.compileSdk.get().toInt() + + defaultConfig { + minSdk = libs.versions.android.minSdk.get().toInt() + + testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner" + } + + buildTypes { + release { + isMinifyEnabled = false + proguardFiles( + getDefaultProguardFile("proguard-android-optimize.txt"), + "proguard-rules.pro" + ) + } + } + compileOptions { + sourceCompatibility = JavaVersion.VERSION_1_8 + targetCompatibility = JavaVersion.VERSION_1_8 + } + kotlin { + compilerOptions { + apiVersion = KotlinVersion.KOTLIN_2_0 + jvmTarget = JvmTarget.JVM_1_8 + } + } + buildFeatures { + compose = true + } + packaging { + resources { + excludes += "/META-INF/{AL2.0,LGPL2.1}" + } + } + publishing { + singleVariant("release") { + withSourcesJar() + } + } +} + +dependencies { + implementation(platform(libs.androidx.compose.bom)) + + implementation(libs.androidx.ktx) + implementation(libs.androidx.lifecycle.compose) + implementation(libs.androidx.activity.compose) + implementation(libs.androidx.appcompat) + implementation(libs.androidx.compose.foundation) + implementation(libs.androidx.compose.ui.ui) + implementation(libs.androidx.compose.ui.toolingPreview) + testImplementation(libs.junit4) + androidTestImplementation(libs.androidx.junit) + androidTestImplementation(libs.androidx.espresso) + androidTestImplementation(libs.androidx.compose.ui.testJunit4) + debugImplementation(libs.androidx.compose.ui.tooling) + debugImplementation(libs.androidx.compose.ui.testManifest) + + implementation(libs.theoplayer) +} + +dokka { + moduleName = "Common" +} + +publishing { + publications { + register("release") { + groupId = "com.theoplayer.android-ui" + artifactId = "android-ui-common" + version = project.version as String + afterEvaluate { + from(components["release"]) + } + } + } +} diff --git a/common/proguard-rules.pro b/common/proguard-rules.pro new file mode 100644 index 0000000..481bb43 --- /dev/null +++ b/common/proguard-rules.pro @@ -0,0 +1,21 @@ +# Add project specific ProGuard rules here. +# You can control the set of applied configuration files using the +# proguardFiles setting in build.gradle. +# +# For more details, see +# http://developer.android.com/guide/developing/tools/proguard.html + +# If your project uses WebView with JS, uncomment the following +# and specify the fully qualified class name to the JavaScript interface +# class: +#-keepclassmembers class fqcn.of.javascript.interface.for.webview { +# public *; +#} + +# Uncomment this to preserve the line number information for +# debugging stack traces. +#-keepattributes SourceFile,LineNumberTable + +# If you keep the line number information, uncomment this to +# hide the original source file name. +#-renamesourcefileattribute SourceFile \ No newline at end of file diff --git a/common/src/main/AndroidManifest.xml b/common/src/main/AndroidManifest.xml new file mode 100644 index 0000000..fa57d87 --- /dev/null +++ b/common/src/main/AndroidManifest.xml @@ -0,0 +1,2 @@ + + \ No newline at end of file diff --git a/ui/src/main/java/com/theoplayer/android/ui/FullscreenHandler.kt b/common/src/main/java/com/theoplayer/android/ui/FullscreenHandler.kt similarity index 100% rename from ui/src/main/java/com/theoplayer/android/ui/FullscreenHandler.kt rename to common/src/main/java/com/theoplayer/android/ui/FullscreenHandler.kt diff --git a/ui/src/main/java/com/theoplayer/android/ui/Modifiers.kt b/common/src/main/java/com/theoplayer/android/ui/Modifiers.kt similarity index 98% rename from ui/src/main/java/com/theoplayer/android/ui/Modifiers.kt rename to common/src/main/java/com/theoplayer/android/ui/Modifiers.kt index a336f00..b651e5c 100644 --- a/ui/src/main/java/com/theoplayer/android/ui/Modifiers.kt +++ b/common/src/main/java/com/theoplayer/android/ui/Modifiers.kt @@ -23,9 +23,11 @@ import kotlinx.coroutines.coroutineScope import kotlinx.coroutines.currentCoroutineContext import kotlinx.coroutines.isActive import kotlinx.coroutines.launch +import org.jetbrains.annotations.ApiStatus import kotlin.math.roundToInt -internal fun Modifier.toggleControlsOnTap( +@ApiStatus.Internal +fun Modifier.toggleControlsOnTap( controlsVisible: State, showControlsTemporarily: () -> Unit, hideControls: () -> Unit @@ -73,7 +75,8 @@ private suspend fun PointerInputScope.detectAnyPointerEvent( } } -internal fun Modifier.constrainedAspectRatio( +@ApiStatus.Internal +fun Modifier.constrainedAspectRatio( @FloatRange(from = 0.0, fromInclusive = false) ratio: Float, matchHeightConstraintsFirst: Boolean = false diff --git a/ui/src/main/java/com/theoplayer/android/ui/Player.kt b/common/src/main/java/com/theoplayer/android/ui/Player.kt similarity index 98% rename from ui/src/main/java/com/theoplayer/android/ui/Player.kt rename to common/src/main/java/com/theoplayer/android/ui/Player.kt index ca4986d..45ddc46 100644 --- a/ui/src/main/java/com/theoplayer/android/ui/Player.kt +++ b/common/src/main/java/com/theoplayer/android/ui/Player.kt @@ -52,6 +52,7 @@ import com.theoplayer.android.api.player.track.texttrack.TextTrack import com.theoplayer.android.api.player.track.texttrack.TextTrackKind import com.theoplayer.android.api.player.track.texttrack.TextTrackMode import com.theoplayer.android.api.source.SourceDescription +import org.jetbrains.annotations.ApiStatus import com.theoplayer.android.api.event.track.mediatrack.audio.list.AddTrackEvent as AudioAddTrackEvent import com.theoplayer.android.api.event.track.mediatrack.audio.list.RemoveTrackEvent as AudioRemoveTrackEvent import com.theoplayer.android.api.event.track.mediatrack.audio.list.TrackListChangeEvent as AudioTrackListChangeEvent @@ -65,10 +66,11 @@ import com.theoplayer.android.api.event.track.texttrack.list.RemoveTrackEvent as import com.theoplayer.android.api.event.track.texttrack.list.TrackListChangeEvent as TextTrackListChangeEvent import com.theoplayer.android.api.player.Player as THEOplayer -internal val LocalPlayer = staticCompositionLocalOf { null } +@ApiStatus.Internal +val LocalPlayer = staticCompositionLocalOf { null } /** - * A player holding a [THEOplayer] instance that can be hosted by a [DefaultUI] or [UIController]. + * A player holding a [THEOplayer] instance that can be hosted by a [com.theoplayer.android.ui.DefaultUI] or [com.theoplayer.android.ui.UIController]. * * All properties are backed by [State] or [MutableState] objects, so reads from within a * [Composable] function will automatically subscribe to changes of that property. @@ -86,7 +88,7 @@ interface Player { /** * Returns the wrapped THEOplayer view. * - * *DO NOT* use this view directly! It will be managed by a [UIController] or [DefaultUI]. + * *DO NOT* use this view directly! It will be managed by a [com.theoplayer.android.ui.UIController] or [com.theoplayer.android.ui.DefaultUI]. */ val theoplayerView: THEOplayerView? @@ -325,7 +327,8 @@ enum class StreamType { Dvr } -internal class PlayerImpl(override val theoplayerView: THEOplayerView?) : Player { +@ApiStatus.Internal +class PlayerImpl(override val theoplayerView: THEOplayerView?) : Player { override val player = theoplayerView?.player override val ads = theoplayerView?.player?.ads override var currentTime by mutableDoubleStateOf(0.0) @@ -504,7 +507,7 @@ internal class PlayerImpl(override val theoplayerView: THEOplayerView?) : Player } } - val fullscreenListener = + private val fullscreenListener = FullscreenHandler.OnFullscreenChangeListener { updateFullscreen() } override var pictureInPicture: Boolean by mutableStateOf(false) diff --git a/ui/src/main/java/com/theoplayer/android/ui/TimeRanges.kt b/common/src/main/java/com/theoplayer/android/ui/TimeRanges.kt similarity index 99% rename from ui/src/main/java/com/theoplayer/android/ui/TimeRanges.kt rename to common/src/main/java/com/theoplayer/android/ui/TimeRanges.kt index 39fe28d..c79c44b 100644 --- a/ui/src/main/java/com/theoplayer/android/ui/TimeRanges.kt +++ b/common/src/main/java/com/theoplayer/android/ui/TimeRanges.kt @@ -47,4 +47,4 @@ data class TimeRanges(private val ranges: List> return TimeRanges(ranges.map { range -> range.start..range.end }) } } -} +} \ No newline at end of file diff --git a/ui/src/main/java/com/theoplayer/android/ui/Util.kt b/common/src/main/java/com/theoplayer/android/ui/Util.kt similarity index 97% rename from ui/src/main/java/com/theoplayer/android/ui/Util.kt rename to common/src/main/java/com/theoplayer/android/ui/Util.kt index 95b8502..57e36e2 100644 --- a/ui/src/main/java/com/theoplayer/android/ui/Util.kt +++ b/common/src/main/java/com/theoplayer/android/ui/Util.kt @@ -1,18 +1,18 @@ -package com.theoplayer.android.ui - -import android.app.Activity -import android.content.pm.PackageManager -import android.os.Build - -// From android.content.pm.ActivityInfo -private const val FLAG_SUPPORTS_PICTURE_IN_PICTURE = 0x400000 - -/** - * Check if the given activity supports [Activity.enterPictureInPictureMode]. - */ -internal fun Activity.supportsPictureInPictureMode(): Boolean { - if (Build.VERSION.SDK_INT < Build.VERSION_CODES.N) return false - if (!packageManager.hasSystemFeature(PackageManager.FEATURE_PICTURE_IN_PICTURE)) return false - val info = packageManager.getActivityInfo(componentName, 0) - return (info.flags and FLAG_SUPPORTS_PICTURE_IN_PICTURE) != 0 +package com.theoplayer.android.ui + +import android.app.Activity +import android.content.pm.PackageManager +import android.os.Build + +// From android.content.pm.ActivityInfo +private const val FLAG_SUPPORTS_PICTURE_IN_PICTURE = 0x400000 + +/** + * Check if the given activity supports [Activity.enterPictureInPictureMode]. + */ +internal fun Activity.supportsPictureInPictureMode(): Boolean { + if (Build.VERSION.SDK_INT < Build.VERSION_CODES.N) return false + if (!packageManager.hasSystemFeature(PackageManager.FEATURE_PICTURE_IN_PICTURE)) return false + val info = packageManager.getActivityInfo(componentName, 0) + return (info.flags and FLAG_SUPPORTS_PICTURE_IN_PICTURE) != 0 } \ No newline at end of file diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 70571fa..dca0e47 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -24,6 +24,7 @@ androidx-lifecycle-compose = { group = "androidx.lifecycle", name = "lifecycle-r androidx-activity-compose = { group = "androidx.activity", name = "activity-compose", version.ref = "activity-compose" } androidx-appcompat = { group = "androidx.appcompat", name = "appcompat", version.ref = "appcompat" } androidx-compose-bom = { group = "androidx.compose", name = "compose-bom", version.ref = "compose-bom" } +androidx-compose-foundation = { group = "androidx.compose.foundation", name = "foundation" } androidx-compose-material3 = { group = "androidx.compose.material3", name = "material3" } androidx-compose-material-iconsExtended = { group = "androidx.compose.material", name = "material-icons-extended" } androidx-compose-ui-ui = { group = "androidx.compose.ui", name = "ui" } diff --git a/settings.gradle.kts b/settings.gradle.kts index ede4760..38ad997 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -15,4 +15,5 @@ dependencyResolutionManagement { } rootProject.name = "THEOplayer Open Video UI for Android" include(":app") +include(":common") include(":ui") diff --git a/ui/build.gradle.kts b/ui/build.gradle.kts index af840ff..dc25bb3 100644 --- a/ui/build.gradle.kts +++ b/ui/build.gradle.kts @@ -85,9 +85,12 @@ dependencies { debugImplementation(libs.androidx.compose.ui.tooling) debugImplementation(libs.androidx.compose.ui.testManifest) + api(project(":common")) api(libs.theoplayer) dokkaPlugin(libs.dokka.plugin) + dokka(project(":common")) + dokka(project) } dokka {