Merge branch 'master' into feat_android_auto_browse

This commit is contained in:
advplyr
2025-01-11 11:10:24 -06:00
77 changed files with 2787 additions and 1702 deletions
+5 -7
View File
@@ -28,13 +28,13 @@ android {
kotlinOptions {
freeCompilerArgs = ['-Xjvm-default=all']
}
compileSdkVersion rootProject.ext.compileSdkVersion
defaultConfig {
compileSdkVersion rootProject.ext.compileSdkVersion
defaultConfig {
applicationId "com.audiobookshelf.app"
minSdkVersion rootProject.ext.minSdkVersion
targetSdkVersion rootProject.ext.targetSdkVersion
versionCode 105
versionName "0.9.74-beta"
versionCode 108
versionName "0.9.77-beta"
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
manifestPlaceholders = [
"appAuthRedirectScheme": "com.audiobookshelf.app"
@@ -64,8 +64,6 @@ repositories {
dirs '../capacitor-cordova-android-plugins/src/main/libs', 'libs'
}
mavenCentral()
// TODO: Temporarily using SNAPSHOT version of Simple Storage that resolves https://github.com/anggrayudi/SimpleStorage/issues/133
maven { url "https://oss.sonatype.org/content/repositories/snapshots" }
}
configurations.configureEach {
@@ -120,7 +118,7 @@ dependencies {
implementation 'io.github.pilgr:paperdb:2.7.2'
// Simple Storage
implementation "com.anggrayudi:storage:1.5.5-SNAPSHOT"
implementation "com.anggrayudi:storage:1.5.6"
// OK HTTP
implementation 'com.squareup.okhttp3:okhttp:4.9.2'
+1
View File
@@ -10,6 +10,7 @@ android {
apply from: "../capacitor-cordova-android-plugins/cordova.variables.gradle"
dependencies {
implementation project(':byteowls-capacitor-filesharer')
implementation project(':capacitor-community-volume-buttons')
implementation project(':capacitor-app')
implementation project(':capacitor-browser')
implementation project(':capacitor-clipboard')
+1
View File
@@ -6,6 +6,7 @@
<!-- Permissions -->
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
<uses-permission android:name="android.permission.FOREGROUND_SERVICE_MEDIA_PLAYBACK" />
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission
android:name="android.permission.WRITE_EXTERNAL_STORAGE"
@@ -3,6 +3,10 @@
"pkg": "@byteowls/capacitor-filesharer",
"classpath": "com.byteowls.capacitor.filesharer.FileSharerPlugin"
},
{
"pkg": "@capacitor-community/volume-buttons",
"classpath": "com.ryltsov.alex.plugins.volume.buttons.VolumeButtonsPlugin"
},
{
"pkg": "@capacitor/app",
"classpath": "com.capacitorjs.plugins.app.AppPlugin"
@@ -18,6 +18,7 @@ import com.audiobookshelf.app.device.DeviceManager
import com.fasterxml.jackson.annotation.JsonIgnore
import com.fasterxml.jackson.annotation.JsonIgnoreProperties
import com.audiobookshelf.app.player.PLAYMETHOD_LOCAL
import java.io.File
import java.util.*
@JsonIgnoreProperties(ignoreUnknown = true)
@@ -78,6 +79,27 @@ class LocalLibraryItem(
}
}
@JsonIgnore
fun hasTracks(episode:PodcastEpisode?): Boolean {
var audioTracks = media.getAudioTracks() as MutableList<AudioTrack>
if (episode != null) { // Get podcast episode audio track
episode.audioTrack?.let { at -> mutableListOf(at) }?.let { tracks -> audioTracks = tracks }
}
if (audioTracks.size == 0) return false
audioTracks.forEach {
// Check that metadata is not null
if (it.metadata === null) {
return false
}
// Check that file exists
val file = File(it.metadata!!.path)
if (!file.exists()) {
return false
}
}
return true
}
@JsonIgnore
fun getPlaybackSession(episode:PodcastEpisode?, deviceInfo:DeviceInfo):PlaybackSession {
val localEpisodeId = episode?.id
@@ -335,11 +335,10 @@ class PlayerNotificationService : MediaBrowserServiceCompat() {
val mediaDescriptionBuilder = MediaDescriptionCompat.Builder()
.setExtras(extra)
.setTitle(currentPlaybackSession!!.displayTitle)
.setIconUri(coverUri)
bitmap?.let {
mediaDescriptionBuilder.setIconBitmap(it)
}
} ?: mediaDescriptionBuilder.setIconUri(coverUri)
return mediaDescriptionBuilder.build()
}
@@ -903,16 +902,18 @@ class PlayerNotificationService : MediaBrowserServiceCompat() {
fun closePlayback(calledOnError:Boolean? = false) {
Log.d(tag, "closePlayback")
val config = DeviceManager.serverConnectionConfig
val isLocal = mediaProgressSyncer.currentIsLocal
val currentSessionId = mediaProgressSyncer.currentSessionId
if (mediaProgressSyncer.listeningTimerRunning) {
Log.i(tag, "About to close playback so stopping media progress syncer first")
mediaProgressSyncer.stop(calledOnError == false) { // If closing on error then do not sync progress (causes exception)
Log.d(tag, "Media Progress syncer stopped")
// If not local session then close on server
if (!isLocal && currentSessionId != "") {
apiHandler.closePlaybackSession(currentSessionId) {
apiHandler.closePlaybackSession(currentSessionId, config) {
Log.d(tag, "Closed playback session $currentSessionId")
}
}
@@ -920,7 +921,7 @@ class PlayerNotificationService : MediaBrowserServiceCompat() {
} else {
// If not local session then close on server
if (!isLocal && currentSessionId != "") {
apiHandler.closePlaybackSession(currentSessionId) {
apiHandler.closePlaybackSession(currentSessionId, config) {
Log.d(tag, "Closed playback session $currentSessionId")
}
}
@@ -198,6 +198,9 @@ class AbsAudioPlayer : Plugin() {
return call.resolve(JSObject("{\"error\":\"Podcast episode not found\"}"))
}
}
if (!it.hasTracks(episode)) {
return call.resolve(JSObject("{\"error\":\"No audio files found on device. Download book again to fix.\"}"))
}
Handler(Looper.getMainLooper()).post {
Log.d(tag, "prepareLibraryItem: Preparing Local Media item ${jacksonMapper.writeValueAsString(it)}")
@@ -196,7 +196,11 @@ class AbsFileSystem : Plugin() {
if (localLibraryItem?.folderId?.startsWith("internal-") == true) {
Log.d(tag, "Deleting internal library item at absolutePath $absolutePath")
val file = File(absolutePath)
success = file.deleteRecursively()
success = if (file.exists()) {
file.deleteRecursively()
} else {
true
}
} else {
var subfolderPathToDelete = ""
localLibraryItem?.folderId?.let { folderId ->
@@ -218,7 +222,12 @@ class AbsFileSystem : Plugin() {
}
val docfile = DocumentFileCompat.fromUri(mainActivity, Uri.parse(contentUrl))
success = docfile?.delete() == true
if (docfile?.exists() == true) {
success = docfile.delete() == true
} else {
Log.d(tag, "Folder $contentUrl doesn't exist")
success = true
}
if (subfolderPathToDelete != "") {
Log.d(tag, "Deleting empty subfolder at $subfolderPathToDelete")
@@ -45,10 +45,17 @@ class ApiHandler(var ctx:Context) {
val address = config?.address ?: DeviceManager.serverAddress
val token = config?.token ?: DeviceManager.token
val request = Request.Builder()
.url("${address}$endpoint").addHeader("Authorization", "Bearer $token")
.build()
makeRequest(request, httpClient, cb)
try {
val request = Request.Builder()
.url("${address}$endpoint").addHeader("Authorization", "Bearer $token")
.build()
makeRequest(request, httpClient, cb)
} catch(e: Exception) {
e.printStackTrace()
val jsobj = JSObject()
jsobj.put("error", "Request failed: ${e.message}")
cb(jsobj)
}
}
private fun postRequest(endpoint:String, payload: JSObject?, config:ServerConnectionConfig?, cb: (JSObject) -> Unit) {
@@ -58,23 +65,38 @@ class ApiHandler(var ctx:Context) {
val requestBody = payload?.toString()?.toRequestBody(mediaType) ?: EMPTY_REQUEST
val requestUrl = "${address}$endpoint"
Log.d(tag, "postRequest to $requestUrl")
val request = Request.Builder().post(requestBody)
.url(requestUrl).addHeader("Authorization", "Bearer ${token}")
.build()
makeRequest(request, null, cb)
try {
val request = Request.Builder().post(requestBody)
.url(requestUrl).addHeader("Authorization", "Bearer ${token}")
.build()
makeRequest(request, null, cb)
} catch(e: Exception) {
e.printStackTrace()
val jsobj = JSObject()
jsobj.put("error", "Request failed: ${e.message}")
cb(jsobj)
}
}
private fun patchRequest(endpoint:String, payload: JSObject, cb: (JSObject) -> Unit) {
val mediaType = "application/json; charset=utf-8".toMediaType()
val requestBody = payload.toString().toRequestBody(mediaType)
val request = Request.Builder().patch(requestBody)
.url("${DeviceManager.serverAddress}$endpoint").addHeader("Authorization", "Bearer ${DeviceManager.token}")
.build()
makeRequest(request, null, cb)
try {
val request = Request.Builder().patch(requestBody)
.url("${DeviceManager.serverAddress}$endpoint").addHeader("Authorization", "Bearer ${DeviceManager.token}")
.build()
makeRequest(request, null, cb)
} catch(e: Exception) {
e.printStackTrace()
val jsobj = JSObject()
jsobj.put("error", "Request failed: ${e.message}")
cb(jsobj)
}
}
private fun makeRequest(request:Request, httpClient:OkHttpClient?, cb: (JSObject) -> Unit) {
val client = httpClient ?: defaultClient
client.newCall(request).enqueue(object : Callback {
override fun onFailure(call: Call, e: IOException) {
Log.d(tag, "FAILURE TO CONNECT")
@@ -424,9 +446,9 @@ class ApiHandler(var ctx:Context) {
}
}
fun closePlaybackSession(playbackSessionId:String, cb: (Boolean) -> Unit) {
fun closePlaybackSession(playbackSessionId:String, config:ServerConnectionConfig?, cb: (Boolean) -> Unit) {
Log.d(tag, "closePlaybackSession: playbackSessionId=$playbackSessionId")
postRequest("/api/session/$playbackSessionId/close", null, null) {
postRequest("/api/session/$playbackSessionId/close", null, config) {
cb(true)
}
}
+1 -1
View File
@@ -1,7 +1,7 @@
// Top-level build file where you can add configuration options common to all sub-projects/modules.
buildscript {
ext.kotlin_version = '1.7.20'
ext.kotlin_version = '2.0.0'
repositories {
google()
+3
View File
@@ -5,6 +5,9 @@ project(':capacitor-android').projectDir = new File('../node_modules/@capacitor/
include ':byteowls-capacitor-filesharer'
project(':byteowls-capacitor-filesharer').projectDir = new File('../node_modules/@byteowls/capacitor-filesharer/android')
include ':capacitor-community-volume-buttons'
project(':capacitor-community-volume-buttons').projectDir = new File('../node_modules/@capacitor-community/volume-buttons/android')
include ':capacitor-app'
project(':capacitor-app').projectDir = new File('../node_modules/@capacitor/app/android')
+5 -5
View File
@@ -1,9 +1,9 @@
ext {
minSdkVersion = 24
compileSdkVersion = 34
targetSdkVersion = 33
targetSdkVersion = 34
androidxActivityVersion = '1.7.0'
androidxAppCompatVersion = '1.6.1'
androidxAppCompatVersion = '1.7.0'
androidxCoordinatorLayoutVersion = '1.2.0'
androidxCoreVersion = '1.10.0'
androidPlayCore = '1.9.0'
@@ -13,8 +13,8 @@ ext {
androidxEspressoCoreVersion = '3.5.1'
cordovaAndroidVersion = '10.1.1'
androidx_car_version = '1.0.0-alpha7'
androidx_core_ktx_version = '1.12.0'
androidx_media_version = '1.6.0'
androidx_core_ktx_version = '1.13.1'
androidx_media_version = '1.7.0'
androidx_preference_version = '1.1.1'
androidx_test_runner_version = '1.3.0'
arch_lifecycle_version = '2.2.0'
@@ -27,7 +27,7 @@ ext {
gradle_version = '3.1.4'
gson_version = '2.8.5'
junit_version = '4.13'
kotlin_version = '1.8.10'
kotlin_version = '2.0.0'
kotlin_coroutines_version = '1.6.4'
multidex_version = '1.0.3'
play_services_auth_version = '18.1.0'