mirror of
https://github.com/advplyr/audiobookshelf-app.git
synced 2026-08-08 21:08:43 +02:00
Compare commits
2
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
910b3a2a17 | ||
|
|
916da91ccb |
@@ -29,8 +29,8 @@ android {
|
|||||||
applicationId "com.audiobookshelf.app"
|
applicationId "com.audiobookshelf.app"
|
||||||
minSdkVersion rootProject.ext.minSdkVersion
|
minSdkVersion rootProject.ext.minSdkVersion
|
||||||
targetSdkVersion rootProject.ext.targetSdkVersion
|
targetSdkVersion rootProject.ext.targetSdkVersion
|
||||||
versionCode 77
|
versionCode 78
|
||||||
versionName "0.9.47-beta"
|
versionName "0.9.48-beta"
|
||||||
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
|
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
|
||||||
aaptOptions {
|
aaptOptions {
|
||||||
// Files and dirs to omit from the packaged assets dir, modified to accommodate modern web apps.
|
// Files and dirs to omit from the packaged assets dir, modified to accommodate modern web apps.
|
||||||
|
|||||||
@@ -8,6 +8,10 @@ import com.audiobookshelf.app.device.DeviceManager
|
|||||||
import com.audiobookshelf.app.server.ApiHandler
|
import com.audiobookshelf.app.server.ApiHandler
|
||||||
import java.util.*
|
import java.util.*
|
||||||
import io.paperdb.Paper
|
import io.paperdb.Paper
|
||||||
|
import kotlinx.coroutines.coroutineScope
|
||||||
|
import kotlinx.coroutines.runBlocking
|
||||||
|
import kotlin.coroutines.resume
|
||||||
|
import kotlin.coroutines.suspendCoroutine
|
||||||
|
|
||||||
class MediaManager(var apiHandler: ApiHandler, var ctx: Context) {
|
class MediaManager(var apiHandler: ApiHandler, var ctx: Context) {
|
||||||
val tag = "MediaManager"
|
val tag = "MediaManager"
|
||||||
@@ -137,6 +141,48 @@ class MediaManager(var apiHandler: ApiHandler, var ctx: Context) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
suspend fun checkServerConnection(config:ServerConnectionConfig) : Boolean {
|
||||||
|
var successfulPing = false
|
||||||
|
suspendCoroutine<Boolean> { cont ->
|
||||||
|
apiHandler.pingServer(config) {
|
||||||
|
Log.d(tag, "checkServerConnection: Checked server conn for ${config.address} result = $it")
|
||||||
|
successfulPing = it
|
||||||
|
cont.resume(it)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return successfulPing
|
||||||
|
}
|
||||||
|
|
||||||
|
fun checkSetValidServerConnectionConfig(cb: (Boolean) -> Unit) = runBlocking {
|
||||||
|
if (!apiHandler.isOnline()) cb(false)
|
||||||
|
else {
|
||||||
|
coroutineScope {
|
||||||
|
var hasValidConn = false
|
||||||
|
|
||||||
|
// First check if the current selected config is pingable
|
||||||
|
DeviceManager.serverConnectionConfig?.let {
|
||||||
|
hasValidConn = checkServerConnection(it)
|
||||||
|
Log.d(tag, "checkSetValidServerConnectionConfig: Current config ${DeviceManager.serverAddress} is pingable? $hasValidConn")
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!hasValidConn) {
|
||||||
|
// Loop through available configs and check if can connect
|
||||||
|
for (config: ServerConnectionConfig in DeviceManager.deviceData.serverConnectionConfigs) {
|
||||||
|
val result = checkServerConnection(config)
|
||||||
|
|
||||||
|
if (result) {
|
||||||
|
hasValidConn = true
|
||||||
|
DeviceManager.serverConnectionConfig = config
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
cb(hasValidConn)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// TODO: Load currently listening category for local items
|
// TODO: Load currently listening category for local items
|
||||||
fun loadLocalCategory():List<LibraryCategory> {
|
fun loadLocalCategory():List<LibraryCategory> {
|
||||||
val localBooks = DeviceManager.dbManager.getLocalLibraryItems("book")
|
val localBooks = DeviceManager.dbManager.getLocalLibraryItems("book")
|
||||||
@@ -158,35 +204,32 @@ class MediaManager(var apiHandler: ApiHandler, var ctx: Context) {
|
|||||||
val localCategories = loadLocalCategory()
|
val localCategories = loadLocalCategory()
|
||||||
cats.addAll(localCategories)
|
cats.addAll(localCategories)
|
||||||
|
|
||||||
// Connected to server and has internet - load other cats
|
// Check if any valid server connection if not use locally downloaded books
|
||||||
if (apiHandler.isOnline() && (DeviceManager.isConnectedToServer || DeviceManager.hasLastServerConnectionConfig)) {
|
checkSetValidServerConnectionConfig { isConnected ->
|
||||||
if (!DeviceManager.isConnectedToServer) {
|
if (isConnected) {
|
||||||
DeviceManager.serverConnectionConfig = DeviceManager.deviceData.getLastServerConnectionConfig()
|
serverConfigIdUsed = DeviceManager.serverConnectionConfigId
|
||||||
Log.d(tag, "Not connected to server, set last server \"${DeviceManager.serverAddress}\"")
|
|
||||||
}
|
|
||||||
|
|
||||||
serverConfigIdUsed = DeviceManager.serverConnectionConfigId
|
loadLibraries { libraries ->
|
||||||
|
val library = libraries[0]
|
||||||
|
Log.d(tag, "Loading categories for library ${library.name} - ${library.id} - ${library.mediaType}")
|
||||||
|
|
||||||
loadLibraries { libraries ->
|
loadLibraryCategories(library.id) { libraryCategories ->
|
||||||
val library = libraries[0]
|
|
||||||
Log.d(tag, "Loading categories for library ${library.name} - ${library.id} - ${library.mediaType}")
|
|
||||||
|
|
||||||
loadLibraryCategories(library.id) { libraryCategories ->
|
// Only using book or podcast library categories for now
|
||||||
|
libraryCategories.forEach {
|
||||||
// Only using book or podcast library categories for now
|
// Log.d(tag, "Found library category ${it.label} with type ${it.type}")
|
||||||
libraryCategories.forEach {
|
if (it.type == library.mediaType) {
|
||||||
// Log.d(tag, "Found library category ${it.label} with type ${it.type}")
|
// Log.d(tag, "Using library category ${it.id}")
|
||||||
if (it.type == library.mediaType) {
|
cats.add(it)
|
||||||
// Log.d(tag, "Using library category ${it.id}")
|
}
|
||||||
cats.add(it)
|
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
cb(cats)
|
cb(cats)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
} else { // Not connected/no internet sent downloaded cats only
|
||||||
|
cb(cats)
|
||||||
}
|
}
|
||||||
} else { // Not connected/no internet sent downloaded cats only
|
|
||||||
cb(cats)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -19,11 +19,13 @@ import okhttp3.MediaType.Companion.toMediaType
|
|||||||
import okhttp3.RequestBody.Companion.toRequestBody
|
import okhttp3.RequestBody.Companion.toRequestBody
|
||||||
import org.json.JSONObject
|
import org.json.JSONObject
|
||||||
import java.io.IOException
|
import java.io.IOException
|
||||||
|
import java.util.concurrent.TimeUnit
|
||||||
|
|
||||||
class ApiHandler(var ctx:Context) {
|
class ApiHandler(var ctx:Context) {
|
||||||
val tag = "ApiHandler"
|
val tag = "ApiHandler"
|
||||||
|
|
||||||
private var client = OkHttpClient()
|
private var defaultClient = OkHttpClient()
|
||||||
|
private var pingClient = OkHttpClient.Builder().callTimeout(3, TimeUnit.SECONDS).build()
|
||||||
var jacksonMapper = jacksonObjectMapper().enable(JsonReadFeature.ALLOW_UNESCAPED_CONTROL_CHARS.mappedFeature())
|
var jacksonMapper = jacksonObjectMapper().enable(JsonReadFeature.ALLOW_UNESCAPED_CONTROL_CHARS.mappedFeature())
|
||||||
|
|
||||||
var storageSharedPreferences: SharedPreferences? = null
|
var storageSharedPreferences: SharedPreferences? = null
|
||||||
@@ -33,11 +35,14 @@ class ApiHandler(var ctx:Context) {
|
|||||||
data class MediaProgressSyncResponsePayload(val numServerProgressUpdates:Int, val localProgressUpdates:List<LocalMediaProgress>)
|
data class MediaProgressSyncResponsePayload(val numServerProgressUpdates:Int, val localProgressUpdates:List<LocalMediaProgress>)
|
||||||
data class LocalMediaProgressSyncResultsPayload(var numLocalMediaProgressForServer:Int, var numServerProgressUpdates:Int, var numLocalProgressUpdates:Int)
|
data class LocalMediaProgressSyncResultsPayload(var numLocalMediaProgressForServer:Int, var numServerProgressUpdates:Int, var numLocalProgressUpdates:Int)
|
||||||
|
|
||||||
fun getRequest(endpoint:String, cb: (JSObject) -> Unit) {
|
fun getRequest(endpoint:String, httpClient:OkHttpClient?, config:ServerConnectionConfig?, cb: (JSObject) -> Unit) {
|
||||||
|
val address = config?.address ?: DeviceManager.serverAddress
|
||||||
|
val token = config?.token ?: DeviceManager.token
|
||||||
|
|
||||||
val request = Request.Builder()
|
val request = Request.Builder()
|
||||||
.url("${DeviceManager.serverAddress}$endpoint").addHeader("Authorization", "Bearer ${DeviceManager.token}")
|
.url("${address}$endpoint").addHeader("Authorization", "Bearer $token")
|
||||||
.build()
|
.build()
|
||||||
makeRequest(request, cb)
|
makeRequest(request, httpClient, cb)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun postRequest(endpoint:String, payload: JSObject, cb: (JSObject) -> Unit) {
|
fun postRequest(endpoint:String, payload: JSObject, cb: (JSObject) -> Unit) {
|
||||||
@@ -46,7 +51,7 @@ class ApiHandler(var ctx:Context) {
|
|||||||
val request = Request.Builder().post(requestBody)
|
val request = Request.Builder().post(requestBody)
|
||||||
.url("${DeviceManager.serverAddress}$endpoint").addHeader("Authorization", "Bearer ${DeviceManager.token}")
|
.url("${DeviceManager.serverAddress}$endpoint").addHeader("Authorization", "Bearer ${DeviceManager.token}")
|
||||||
.build()
|
.build()
|
||||||
makeRequest(request, cb)
|
makeRequest(request, null, cb)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun patchRequest(endpoint:String, payload: JSObject, cb: (JSObject) -> Unit) {
|
fun patchRequest(endpoint:String, payload: JSObject, cb: (JSObject) -> Unit) {
|
||||||
@@ -55,7 +60,7 @@ class ApiHandler(var ctx:Context) {
|
|||||||
val request = Request.Builder().patch(requestBody)
|
val request = Request.Builder().patch(requestBody)
|
||||||
.url("${DeviceManager.serverAddress}$endpoint").addHeader("Authorization", "Bearer ${DeviceManager.token}")
|
.url("${DeviceManager.serverAddress}$endpoint").addHeader("Authorization", "Bearer ${DeviceManager.token}")
|
||||||
.build()
|
.build()
|
||||||
makeRequest(request, cb)
|
makeRequest(request, null, cb)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun isOnline(): Boolean {
|
fun isOnline(): Boolean {
|
||||||
@@ -76,7 +81,8 @@ class ApiHandler(var ctx:Context) {
|
|||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
fun makeRequest(request:Request, cb: (JSObject) -> Unit) {
|
fun makeRequest(request:Request, httpClient:OkHttpClient?, cb: (JSObject) -> Unit) {
|
||||||
|
val client = httpClient ?: defaultClient
|
||||||
client.newCall(request).enqueue(object : Callback {
|
client.newCall(request).enqueue(object : Callback {
|
||||||
override fun onFailure(call: Call, e: IOException) {
|
override fun onFailure(call: Call, e: IOException) {
|
||||||
Log.d(tag, "FAILURE TO CONNECT")
|
Log.d(tag, "FAILURE TO CONNECT")
|
||||||
@@ -114,7 +120,7 @@ class ApiHandler(var ctx:Context) {
|
|||||||
|
|
||||||
fun getLibraries(cb: (List<Library>) -> Unit) {
|
fun getLibraries(cb: (List<Library>) -> Unit) {
|
||||||
val mapper = jacksonMapper
|
val mapper = jacksonMapper
|
||||||
getRequest("/api/libraries") {
|
getRequest("/api/libraries", null,null) {
|
||||||
val libraries = mutableListOf<Library>()
|
val libraries = mutableListOf<Library>()
|
||||||
if (it.has("value")) {
|
if (it.has("value")) {
|
||||||
val array = it.getJSONArray("value")
|
val array = it.getJSONArray("value")
|
||||||
@@ -128,7 +134,7 @@ class ApiHandler(var ctx:Context) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fun getLibraryItem(libraryItemId:String, cb: (LibraryItem) -> Unit) {
|
fun getLibraryItem(libraryItemId:String, cb: (LibraryItem) -> Unit) {
|
||||||
getRequest("/api/items/$libraryItemId?expanded=1") {
|
getRequest("/api/items/$libraryItemId?expanded=1", null, null) {
|
||||||
val libraryItem = jacksonMapper.readValue<LibraryItem>(it.toString())
|
val libraryItem = jacksonMapper.readValue<LibraryItem>(it.toString())
|
||||||
cb(libraryItem)
|
cb(libraryItem)
|
||||||
}
|
}
|
||||||
@@ -137,14 +143,14 @@ class ApiHandler(var ctx:Context) {
|
|||||||
fun getLibraryItemWithProgress(libraryItemId:String, episodeId:String?, cb: (LibraryItem) -> Unit) {
|
fun getLibraryItemWithProgress(libraryItemId:String, episodeId:String?, cb: (LibraryItem) -> Unit) {
|
||||||
var requestUrl = "/api/items/$libraryItemId?expanded=1&include=progress"
|
var requestUrl = "/api/items/$libraryItemId?expanded=1&include=progress"
|
||||||
if (!episodeId.isNullOrEmpty()) requestUrl += "&episode=$episodeId"
|
if (!episodeId.isNullOrEmpty()) requestUrl += "&episode=$episodeId"
|
||||||
getRequest(requestUrl) {
|
getRequest(requestUrl, null, null) {
|
||||||
val libraryItem = jacksonMapper.readValue<LibraryItem>(it.toString())
|
val libraryItem = jacksonMapper.readValue<LibraryItem>(it.toString())
|
||||||
cb(libraryItem)
|
cb(libraryItem)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun getLibraryItems(libraryId:String, cb: (List<LibraryItem>) -> Unit) {
|
fun getLibraryItems(libraryId:String, cb: (List<LibraryItem>) -> Unit) {
|
||||||
getRequest("/api/libraries/$libraryId/items?limit=100&minified=1") {
|
getRequest("/api/libraries/$libraryId/items?limit=100&minified=1", null, null) {
|
||||||
val items = mutableListOf<LibraryItem>()
|
val items = mutableListOf<LibraryItem>()
|
||||||
if (it.has("results")) {
|
if (it.has("results")) {
|
||||||
val array = it.getJSONArray("results")
|
val array = it.getJSONArray("results")
|
||||||
@@ -158,7 +164,7 @@ class ApiHandler(var ctx:Context) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fun getLibraryCategories(libraryId:String, cb: (List<LibraryCategory>) -> Unit) {
|
fun getLibraryCategories(libraryId:String, cb: (List<LibraryCategory>) -> Unit) {
|
||||||
getRequest("/api/libraries/$libraryId/personalized") {
|
getRequest("/api/libraries/$libraryId/personalized", null, null) {
|
||||||
val items = mutableListOf<LibraryCategory>()
|
val items = mutableListOf<LibraryCategory>()
|
||||||
if (it.has("value")) {
|
if (it.has("value")) {
|
||||||
val array = it.getJSONArray("value")
|
val array = it.getJSONArray("value")
|
||||||
@@ -265,7 +271,7 @@ class ApiHandler(var ctx:Context) {
|
|||||||
|
|
||||||
fun getMediaProgress(libraryItemId:String, episodeId:String?, cb: (MediaProgress) -> Unit) {
|
fun getMediaProgress(libraryItemId:String, episodeId:String?, cb: (MediaProgress) -> Unit) {
|
||||||
val endpoint = if(episodeId.isNullOrEmpty()) "/api/me/progress/$libraryItemId" else "/api/me/progress/$libraryItemId/$episodeId"
|
val endpoint = if(episodeId.isNullOrEmpty()) "/api/me/progress/$libraryItemId" else "/api/me/progress/$libraryItemId/$episodeId"
|
||||||
getRequest(endpoint) {
|
getRequest(endpoint, null, null) {
|
||||||
val progress = jacksonMapper.readValue<MediaProgress>(it.toString())
|
val progress = jacksonMapper.readValue<MediaProgress>(it.toString())
|
||||||
cb(progress)
|
cb(progress)
|
||||||
}
|
}
|
||||||
@@ -273,7 +279,7 @@ class ApiHandler(var ctx:Context) {
|
|||||||
|
|
||||||
fun getPlaybackSession(playbackSessionId:String, cb: (PlaybackSession?) -> Unit) {
|
fun getPlaybackSession(playbackSessionId:String, cb: (PlaybackSession?) -> Unit) {
|
||||||
val endpoint = "/api/session/$playbackSessionId"
|
val endpoint = "/api/session/$playbackSessionId"
|
||||||
getRequest(endpoint) {
|
getRequest(endpoint, null, null) {
|
||||||
val err = it.getString("error")
|
val err = it.getString("error")
|
||||||
if (!err.isNullOrEmpty()) {
|
if (!err.isNullOrEmpty()) {
|
||||||
cb(null)
|
cb(null)
|
||||||
@@ -282,4 +288,18 @@ class ApiHandler(var ctx:Context) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun pingServer(config:ServerConnectionConfig, cb: (Boolean) -> Unit) {
|
||||||
|
Log.d(tag, "pingServer: Pinging ${config.address}")
|
||||||
|
getRequest("/ping", pingClient, config) {
|
||||||
|
val success = it.getString("success")
|
||||||
|
if (success.isNullOrEmpty()) {
|
||||||
|
Log.d(tag, "pingServer: Ping ${config.address} Failed")
|
||||||
|
cb(false)
|
||||||
|
} else {
|
||||||
|
Log.d(tag, "pingServer: Ping ${config.address} Successful")
|
||||||
|
cb(true)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+6
-6
@@ -9,12 +9,12 @@ install! 'cocoapods', :disable_input_output_paths => true
|
|||||||
def capacitor_pods
|
def capacitor_pods
|
||||||
pod 'Capacitor', :path => '../../node_modules/@capacitor/ios'
|
pod 'Capacitor', :path => '../../node_modules/@capacitor/ios'
|
||||||
pod 'CapacitorCordova', :path => '../../node_modules/@capacitor/ios'
|
pod 'CapacitorCordova', :path => '../../node_modules/@capacitor/ios'
|
||||||
pod 'CapacitorApp', :path => '../../node_modules/@capacitor/app'
|
pod 'CapacitorApp', :path => '..\..\node_modules\@capacitor\app'
|
||||||
pod 'CapacitorDialog', :path => '../../node_modules/@capacitor/dialog'
|
pod 'CapacitorDialog', :path => '..\..\node_modules\@capacitor\dialog'
|
||||||
pod 'CapacitorHaptics', :path => '../../node_modules/@capacitor/haptics'
|
pod 'CapacitorHaptics', :path => '..\..\node_modules\@capacitor\haptics'
|
||||||
pod 'CapacitorNetwork', :path => '../../node_modules/@capacitor/network'
|
pod 'CapacitorNetwork', :path => '..\..\node_modules\@capacitor\network'
|
||||||
pod 'CapacitorStatusBar', :path => '../../node_modules/@capacitor/status-bar'
|
pod 'CapacitorStatusBar', :path => '..\..\node_modules\@capacitor\status-bar'
|
||||||
pod 'CapacitorStorage', :path => '../../node_modules/@capacitor/storage'
|
pod 'CapacitorStorage', :path => '..\..\node_modules\@capacitor\storage'
|
||||||
end
|
end
|
||||||
|
|
||||||
target 'App' do
|
target 'App' do
|
||||||
|
|||||||
Generated
+1
-1
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "audiobookshelf-app",
|
"name": "audiobookshelf-app",
|
||||||
"version": "0.9.47-beta",
|
"version": "0.9.48-beta",
|
||||||
"lockfileVersion": 2,
|
"lockfileVersion": 2,
|
||||||
"requires": true,
|
"requires": true,
|
||||||
"packages": {
|
"packages": {
|
||||||
|
|||||||
+1
-1
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "audiobookshelf-app",
|
"name": "audiobookshelf-app",
|
||||||
"version": "0.9.47-beta",
|
"version": "0.9.48-beta",
|
||||||
"author": "advplyr",
|
"author": "advplyr",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"dev": "nuxt --hostname 0.0.0.0 --port 1337",
|
"dev": "nuxt --hostname 0.0.0.0 --port 1337",
|
||||||
|
|||||||
Reference in New Issue
Block a user