Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion .idea/misc.xml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

10 changes: 10 additions & 0 deletions .idea/planningMode.xml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

104 changes: 73 additions & 31 deletions app/build.gradle.kts
Original file line number Diff line number Diff line change
@@ -1,12 +1,13 @@
import java.io.FileInputStream
import java.util.Properties
import org.jetbrains.kotlin.gradle.dsl.JvmTarget
import com.android.build.api.variant.FilterConfiguration

plugins {
alias(libs.plugins.android.application)
alias(libs.plugins.kotlin.android)
alias(libs.plugins.kotlin.compose)
alias(libs.plugins.ksp)
alias(libs.plugins.aboutLibraries)
}

kotlin {
Expand All @@ -28,12 +29,12 @@ val appVersion = "1.4.0"

android {
namespace = "com.devson.nvplayer"
compileSdk = 36
compileSdk = 37

defaultConfig {
applicationId = "com.devson.nvplayer"
minSdk = 26
targetSdk = 36
minSdk = 30
targetSdk = 37
versionCode = 140
versionName = appVersion

Expand Down Expand Up @@ -87,18 +88,10 @@ android {
isEnable = true
reset()
include("arm64-v8a", "armeabi-v7a", "x86", "x86_64")
isUniversalApk = true
isUniversalApk = false
}
}
}
applicationVariants.all {
val variant = this
variant.outputs.all {
val outputImpl = this as com.android.build.gradle.internal.api.BaseVariantOutputImpl
val abiName = outputImpl.filters.find { it.filterType == "ABI" }?.identifier ?: "universal"
outputFileName = "NosvedPlayer_v${variant.versionName}-${abiName}.apk"
}
}

compileOptions {
sourceCompatibility = JavaVersion.VERSION_17
Expand Down Expand Up @@ -135,75 +128,124 @@ android {
"**/libavutil.so",
"**/libswresample.so",
"**/libswscale.so",
"**/libpostproc.so"
"**/libpostproc.so",
"**/libc++_shared.so"
)
}
}
ndkVersion = "27.0.12077973"
}

androidComponents {
onVariants { variant ->
variant.outputs.forEach { output ->
val abiName = output.filters.find {
it.filterType == FilterConfiguration.FilterType.ABI
}?.identifier ?: "universal"
val versionName = output.versionName.get()
output.outputFileName.set("NosvedPlayer_v${versionName}-${abiName}.apk")
}
}
}

dependencies {
// Project Modules (Missing from current project structure)
// implementation(project(":core:common"))
// implementation(project(":core:data"))
// implementation(project(":core:domain"))
// implementation(project(":core:media"))
// implementation(project(":core:model"))
// implementation(project(":core:ui"))

// androidx
implementation(libs.androidx.core.ktx)
implementation("androidx.appcompat:appcompat:1.6.1")
implementation(libs.androidx.appcompat)
implementation(libs.androidx.activity.ktx)
implementation(libs.androidx.lifecycle.runtime.ktx)
implementation(libs.androidx.lifecycle.viewModel.ktx)
implementation(libs.androidx.activity.compose)
implementation(platform(libs.androidx.compose.bom))
implementation(libs.androidx.compose.ui)
implementation(libs.androidx.compose.ui.graphics)
implementation(libs.androidx.compose.ui.tooling.preview)
implementation(libs.androidx.constraintlayout)

// Material3
// Material
implementation(libs.google.android.material)
implementation(libs.androidx.compose.material3)
implementation(libs.androidx.compose.material)
implementation(libs.androidx.compose.material.iconsExtended)
implementation(libs.androidx.compose.icons)

// media3
implementation(libs.androidx.media3.common)
implementation(libs.androidx.media3.exoplayer)
implementation(libs.androidx.media3.exoplayer.dash)
implementation(libs.androidx.media3.exoplayer.hls)
implementation(libs.androidx.media3.exoplayer.rtsp)
implementation(libs.androidx.media3.datasource.okhttp)
implementation(libs.androidx.media3.session)
implementation(libs.androidx.media3.transformer)
implementation(libs.androidx.media3.effect)
implementation(libs.androidx.lifecycle.viewmodel.compose)
implementation(libs.androidx.media3.ui)

// nextlib
implementation(libs.nextlib.media3ext)
implementation(libs.nextlib.mediainfo)
implementation(libs.androidx.media3.ui.compose)
implementation(libs.androidx.lifecycle.viewModelCompose)
implementation(libs.github.peerless2012.ass.media)

// custom aar for replacing exoplayer
implementation(files("libs/universal.aar"))
implementation(files("libs/media3ext-release.aar"))
implementation(files("libs/mediainfo-release.aar"))

// FFMPEG kit for Video Utility
implementation("io.github.jamaismagic.ffmpeg:ffmpeg-kit-main-full-gpl-16kb:6.1.4")

// DataStore for Settings
implementation("androidx.datastore:datastore-preferences:1.0.0")
implementation("androidx.datastore:datastore-preferences:1.2.1")

// Room for Database
implementation(libs.androidx.room.runtime)
implementation(libs.androidx.room.ktx)
ksp(libs.androidx.room.compiler)

// Hilt
implementation(libs.hilt.android)
ksp(libs.hilt.compiler)
ksp(libs.kotlin.metadata.jvm)
// kspAndroidTest(libs.hilt.compiler) // Requires kspAndroidTest configuration to be available

// Compose
implementation(libs.androidx.compose.material)
implementation(libs.androidx.compose.foundation)
implementation(libs.androidx.navigation.compose)
implementation(libs.reorderable)

// coil
// coil
implementation(libs.coil.compose)
implementation(libs.coilcore)
implementation(libs.coil.video)

// About Libraries
implementation("com.mikepenz:aboutlibraries-core:${libs.versions.aboutlibraries.get()}")
implementation("com.mikepenz:aboutlibraries-compose-m3:${libs.versions.aboutlibraries.get()}")

// WorkManager for background tasks
implementation(libs.androidx.work.runtime.ktx)

// Networking & IO
implementation(libs.kotlinx.coroutines.android)
implementation(libs.kotlinx.coroutines.guava)
implementation(libs.commons.net)
implementation(libs.smbj)

// documentfile
implementation("androidx.documentfile:documentfile:1.0.1")
implementation("androidx.documentfile:documentfile:1.1.0")

// Test
testImplementation(libs.junit)
androidTestImplementation(libs.androidx.junit)
androidTestImplementation(libs.androidx.espresso.core)
testImplementation(libs.junit4)
testImplementation(libs.kotlinx.coroutines.test)
androidTestImplementation(libs.androidx.test.ext)
androidTestImplementation(libs.androidx.test.espresso.core)
androidTestImplementation(platform(libs.androidx.compose.bom))
androidTestImplementation(libs.androidx.compose.ui.test.junit4)
androidTestImplementation(libs.androidx.compose.ui.test)
debugImplementation(libs.androidx.compose.ui.tooling)
debugImplementation(libs.androidx.compose.ui.test.manifest)
debugImplementation(libs.androidx.compose.ui.testManifest)
}
59 changes: 49 additions & 10 deletions app/src/main/java/com/devson/nvplayer/NosvedApplication.kt
Original file line number Diff line number Diff line change
@@ -1,35 +1,74 @@
package com.devson.nvplayer

import android.app.Application
import coil.ImageLoader
import coil.ImageLoaderFactory
import coil.decode.VideoFrameDecoder
import coil.disk.DiskCache
import coil.memory.MemoryCache
import coil3.ImageLoader
import coil3.SingletonImageLoader
import coil3.PlatformContext
import coil3.video.VideoFrameDecoder
import coil3.disk.DiskCache
import coil3.memory.MemoryCache
import coil3.request.crossfade
import okio.Path.Companion.toOkioPath
import com.devson.nvplayer.util.MediaStoreThumbnailFetcher
import com.devson.nvplayer.util.VideoThumbnailDecoder
import com.devson.nvplayer.util.ThumbnailStrategy
import com.devson.nvplayer.repository.PlaybackSettingsRepository
import com.devson.nvplayer.repository.ThumbnailGenerationStrategy
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.SupervisorJob
import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.stateIn

class NosvedApplication : Application(), ImageLoaderFactory {
class NosvedApplication : Application(), SingletonImageLoader.Factory {

override fun newImageLoader(): ImageLoader {
private val applicationScope = CoroutineScope(SupervisorJob() + Dispatchers.Main)
private val settingsRepo by lazy { PlaybackSettingsRepository(this) }

private val playbackSettings by lazy {
settingsRepo.playbackSettingsFlow.stateIn(
scope = applicationScope,
started = SharingStarted.Eagerly,
initialValue = com.devson.nvplayer.repository.PlaybackSettings(
seekDurationSeconds = 10,
seekBarStyle = "line",
controlIconSize = "medium",
autoPlayEnabled = false
)
)
}

override fun newImageLoader(context: PlatformContext): ImageLoader {
// Determine a sensible disk cache size: 10% of free space, clamped between 50 MB and 500 MB.
val cacheDir = cacheDir.resolve("video_thumbnails_cache")
val freeBytes = cacheDir.parentFile?.freeSpace ?: (200L * 1024 * 1024)
val cacheSizeBytes = (freeBytes * 0.10)
.toLong()
.coerceIn(50L * 1024 * 1024, 500L * 1024 * 1024)

return ImageLoader.Builder(this)
return ImageLoader.Builder(context)
.components {
add(
VideoThumbnailDecoder.Factory(
thumbnailStrategy = {
val settings = playbackSettings.value
when (settings.thumbnailGenerationStrategy) {
ThumbnailGenerationStrategy.FIRST_FRAME -> ThumbnailStrategy.FirstFrame
ThumbnailGenerationStrategy.FRAME_POSITION -> ThumbnailStrategy.FrameAtPercentage(settings.thumbnailFramePosition)
ThumbnailGenerationStrategy.HYBRID -> ThumbnailStrategy.Hybrid(settings.thumbnailFramePosition)
}
}
)
)
add(MediaStoreThumbnailFetcher.Factory())
add(VideoFrameDecoder.Factory())
}
// Disable hardware bitmaps so frames can be decoded safely on background threads
// (WorkManager threads have no active GL context).
// .allowHardware(false)
.memoryCache {
MemoryCache.Builder(this)
.maxSizePercent(0.20) // Use 20% of app memory for in-memory LRU cache
MemoryCache.Builder()
.maxSizePercent(context, 0.20) // Use 20% of app memory for in-memory LRU cache
.build()
}
.diskCache {
Expand Down
26 changes: 25 additions & 1 deletion app/src/main/java/com/devson/nvplayer/navigation/NavGraph.kt
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,9 @@ import androidx.navigation.compose.NavHost
import androidx.navigation.compose.composable
import com.devson.nvplayer.model.Video
import com.devson.nvplayer.ui.screens.settings.AboutScreen
import com.devson.nvplayer.ui.screens.settings.LibrariesScreen
import com.devson.nvplayer.ui.screens.settings.AudioSettingsScreen
import com.devson.nvplayer.ui.screens.settings.SubtitleSettingsScreen
import com.devson.nvplayer.ui.screens.HistoryScreen
import com.devson.nvplayer.ui.screens.HomeScreen
import com.devson.nvplayer.ui.screens.OnboardingScreen
Expand Down Expand Up @@ -138,6 +141,8 @@ fun NavGraph(
onNavigateToPlayerInterface = { navController.navigate(Screen.PlayerInterface.route) },
onNavigateToCustomHome = { navController.navigate(Screen.CustomHome.route) },
onNavigateToGestures = { navController.navigate(Screen.Gestures.route) },
onNavigateToAudioSettings = { navController.navigate(Screen.AudioSettings.route) },
onNavigateToSubtitleSettings = { navController.navigate(Screen.SubtitleSettings.route) },
settingsViewModel = settingsViewModel
)
}
Expand All @@ -163,6 +168,20 @@ fun NavGraph(
)
}

composable(Screen.AudioSettings.route) {
AudioSettingsScreen(
onBack = { safePopBackStack() },
settingsViewModel = settingsViewModel
)
}

composable(Screen.SubtitleSettings.route) {
SubtitleSettingsScreen(
onBack = { safePopBackStack() },
settingsViewModel = settingsViewModel
)
}

composable(Screen.ScanFolders.route) {
com.devson.nvplayer.ui.screens.settings.ScanFoldersScreen(
onBack = { safePopBackStack() },
Expand Down Expand Up @@ -204,10 +223,15 @@ fun NavGraph(
composable(Screen.About.route) {
AboutScreen(
onBack = { safePopBackStack() },
onEnableDeveloperMode = { settingsViewModel.enableDeveloperMode() }
onEnableDeveloperMode = { settingsViewModel.enableDeveloperMode() },
onNavigateToLibraries = { navController.navigate(Screen.Libraries.route) }
)
}

composable(Screen.Libraries.route) {
LibrariesScreen(onBack = { safePopBackStack() })
}

composable(Screen.Logs.route) {
LogScreen(
onBack = { safePopBackStack() }
Expand Down
3 changes: 3 additions & 0 deletions app/src/main/java/com/devson/nvplayer/navigation/Screen.kt
Original file line number Diff line number Diff line change
Expand Up @@ -23,4 +23,7 @@ sealed class Screen(val route: String) {
object CustomHome : Screen("custom_home")
object Gestures : Screen("gestures")
object MediaStoreFinder : Screen("media_store_finder")
object Libraries : Screen("libraries")
object AudioSettings : Screen("audio_settings")
object SubtitleSettings : Screen("subtitle_settings")
}
Loading