mirror of
https://github.com/advplyr/audiobookshelf-app.git
synced 2026-07-27 15:08:40 +02:00
Compare commits
6
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
0254afd5d8 | ||
|
|
853546f3d2 | ||
|
|
b8ac073247 | ||
|
|
56cb4df31a | ||
|
|
7285b0bf26 | ||
|
|
3fdc2e6dcf |
@@ -36,8 +36,8 @@ android {
|
||||
applicationId "com.audiobookshelf.app"
|
||||
minSdkVersion rootProject.ext.minSdkVersion
|
||||
targetSdkVersion rootProject.ext.targetSdkVersion
|
||||
versionCode 116
|
||||
versionName "0.12.0-beta"
|
||||
versionCode 117
|
||||
versionName "0.13.0-beta"
|
||||
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
|
||||
manifestPlaceholders = [
|
||||
"appAuthRedirectScheme": "com.audiobookshelf.app"
|
||||
|
||||
@@ -47,7 +47,7 @@ data class DownloadItemPart(
|
||||
}
|
||||
|
||||
val downloadUri = Uri.parse(downloadUrl)
|
||||
Log.d("DownloadItemPart", "Audio File Destination Uri: $destinationUri | Final Destination Uri: $finalDestinationUri | Download URI $downloadUri")
|
||||
Log.d("DownloadItemPart", "Audio File Destination Uri: $destinationUri | Final Destination Uri: $finalDestinationUri | Server Path $serverPath")
|
||||
return DownloadItemPart(
|
||||
id = DeviceManager.getBase64Id(finalDestinationFile.absolutePath),
|
||||
downloadItemId,
|
||||
|
||||
@@ -119,7 +119,7 @@ class AbsDatabase : Plugin() {
|
||||
|
||||
@PluginMethod
|
||||
fun setCurrentServerConnectionConfig(call:PluginCall) {
|
||||
Log.d(tag, "setCurrentServerConnectionConfig ${call.data}")
|
||||
Log.d(tag, "setCurrentServerConnectionConfig called")
|
||||
val serverConfigPayload = jacksonMapper.readValue<ServerConnConfigPayload>(call.data.toString())
|
||||
var serverConnectionConfig = DeviceManager.deviceData.serverConnectionConfigs.find { it.id == serverConfigPayload.id }
|
||||
|
||||
@@ -558,7 +558,7 @@ class AbsDatabase : Plugin() {
|
||||
|
||||
@PluginMethod
|
||||
fun updateDeviceSettings(call:PluginCall) { // Returns device data
|
||||
Log.d(tag, "updateDeviceSettings ${call.data}")
|
||||
Log.d(tag, "updateDeviceSettings called")
|
||||
val newDeviceSettings = jacksonMapper.readValue<DeviceSettings>(call.data.toString())
|
||||
|
||||
Handler(Looper.getMainLooper()).post {
|
||||
@@ -576,7 +576,7 @@ class AbsDatabase : Plugin() {
|
||||
|
||||
@PluginMethod
|
||||
fun getMediaItemHistory(call:PluginCall) { // Returns device data
|
||||
Log.d(tag, "getMediaItemHistory ${call.data}")
|
||||
Log.d(tag, "getMediaItemHistory called")
|
||||
val mediaId = call.getString("mediaId") ?: ""
|
||||
|
||||
GlobalScope.launch(Dispatchers.IO) {
|
||||
|
||||
@@ -114,7 +114,7 @@
|
||||
import { Capacitor } from '@capacitor/core'
|
||||
import { AbsAudioPlayer } from '@/plugins/capacitor'
|
||||
import { Dialog } from '@capacitor/dialog'
|
||||
import { FastAverageColor } from 'fast-average-color'
|
||||
import { getAverageColorFromCoverUrl } from '@/utils/coverAverageColor'
|
||||
import WrappingMarquee from '@/assets/WrappingMarquee.js'
|
||||
import jumpLabelMixin from '@/mixins/jumpLabel'
|
||||
|
||||
@@ -412,17 +412,14 @@ export default {
|
||||
},
|
||||
async coverImageLoaded(fullCoverUrl) {
|
||||
if (!fullCoverUrl) return
|
||||
|
||||
const fac = new FastAverageColor()
|
||||
fac
|
||||
.getColorAsync(fullCoverUrl)
|
||||
.then((color) => {
|
||||
this.coverRgb = color.rgba
|
||||
this.coverBgIsLight = color.isLight
|
||||
})
|
||||
.catch((e) => {
|
||||
console.log(e)
|
||||
})
|
||||
const avg = await getAverageColorFromCoverUrl(this, fullCoverUrl)
|
||||
if (!avg) {
|
||||
this.coverRgb = 'rgb(55, 56, 56)'
|
||||
this.coverBgIsLight = false
|
||||
} else {
|
||||
this.coverRgb = avg.rgba
|
||||
this.coverBgIsLight = avg.isLight
|
||||
}
|
||||
},
|
||||
clickTitleAndAuthor() {
|
||||
if (!this.showFullscreen) return
|
||||
|
||||
@@ -484,7 +484,7 @@ export default {
|
||||
|
||||
const { value } = await Dialog.confirm({
|
||||
title: this.$strings.HeaderConfirm,
|
||||
message: this.$strings.MessageConfirmDeleteServerConfig,
|
||||
message: this.$strings.MessageConfirmDeleteServerConfig
|
||||
})
|
||||
if (value) {
|
||||
this.processing = true
|
||||
@@ -843,7 +843,7 @@ export default {
|
||||
async setUserAndConnection({ user, userDefaultLibraryId, serverSettings, ereaderDevices }) {
|
||||
if (!user) return
|
||||
|
||||
console.log('Successfully logged in', JSON.stringify(user))
|
||||
console.log('Successfully logged in: ' + user.username)
|
||||
|
||||
this.$store.commit('setServerSettings', serverSettings)
|
||||
this.$store.commit('libraries/setEReaderDevices', ereaderDevices)
|
||||
|
||||
@@ -740,12 +740,12 @@
|
||||
ASSETCATALOG_COMPILER_APPICON_NAME = Icons;
|
||||
CLANG_ENABLE_MODULES = YES;
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
CURRENT_PROJECT_VERSION = 41;
|
||||
CURRENT_PROJECT_VERSION = 43;
|
||||
DEVELOPMENT_TEAM = 7UFJ7D8V6A;
|
||||
INFOPLIST_FILE = App/Info.plist;
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 14.0;
|
||||
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks";
|
||||
MARKETING_VERSION = 0.11.0;
|
||||
MARKETING_VERSION = 0.13.0;
|
||||
OTHER_SWIFT_FLAGS = "$(inherited) \"-D\" \"COCOAPODS\" \"-DDEBUG\"";
|
||||
PRODUCT_BUNDLE_IDENTIFIER = com.audiobookshelf.app.dev;
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
@@ -764,12 +764,12 @@
|
||||
ASSETCATALOG_COMPILER_APPICON_NAME = Icons;
|
||||
CLANG_ENABLE_MODULES = YES;
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
CURRENT_PROJECT_VERSION = 41;
|
||||
CURRENT_PROJECT_VERSION = 43;
|
||||
DEVELOPMENT_TEAM = 7UFJ7D8V6A;
|
||||
INFOPLIST_FILE = App/Info.plist;
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 14.0;
|
||||
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks";
|
||||
MARKETING_VERSION = 0.11.0;
|
||||
MARKETING_VERSION = 0.13.0;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = com.audiobookshelf.app;
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
SWIFT_ACTIVE_COMPILATION_CONDITIONS = "";
|
||||
|
||||
+20
-20
@@ -25,11 +25,11 @@ PODS:
|
||||
- Capacitor
|
||||
- CordovaPlugins (6.2.1):
|
||||
- CapacitorCordova
|
||||
- Realm (10.54.4):
|
||||
- Realm/Headers (= 10.54.4)
|
||||
- Realm/Headers (10.54.4)
|
||||
- RealmSwift (10.54.4):
|
||||
- Realm (= 10.54.4)
|
||||
- Realm (10.54.6):
|
||||
- Realm/Headers (= 10.54.6)
|
||||
- Realm/Headers (10.54.6)
|
||||
- RealmSwift (10.54.6):
|
||||
- Realm (= 10.54.6)
|
||||
- WebnativellcCapacitorFilesharer (7.0.4):
|
||||
- Capacitor
|
||||
|
||||
@@ -89,22 +89,22 @@ EXTERNAL SOURCES:
|
||||
|
||||
SPEC CHECKSUMS:
|
||||
Alamofire: 7193b3b92c74a07f85569e1a6c4f4237291e7496
|
||||
Capacitor: 106e7a4205f4618d582b886a975657c61179138d
|
||||
CapacitorApp: d63334c052278caf5d81585d80b21905c6f93f39
|
||||
CapacitorBrowser: 081852cf532acf77b9d2953f3a88fe5b9711fb06
|
||||
CapacitorClipboard: b98aead5dc7ec595547fc2c5d75bacd2ae3338bc
|
||||
CapacitorCommunityKeepAwake: 00dfd8fa3cca0df003c9a3e2cd7bee678aeec68b
|
||||
CapacitorCommunityVolumeButtons: 8a0443a202ed659688d85f4d44d66f42f62f2b56
|
||||
Capacitor: 03bc7cbdde6a629a8b910a9d7d78c3cc7ed09ea7
|
||||
CapacitorApp: febecbb9582cb353aed037e18ec765141f880fe9
|
||||
CapacitorBrowser: 6299776d496e968505464884d565992faa20444a
|
||||
CapacitorClipboard: 70bfdb42b877b320a6e511ab94fa7a6a55d57ecb
|
||||
CapacitorCommunityKeepAwake: ae762ce29b53147d28cfcaae5273cd1db0c38fc4
|
||||
CapacitorCommunityVolumeButtons: 1b84f7abf29cd9476cef9e8979b2854a64d2eed5
|
||||
CapacitorCordova: 5967b9ba03915ef1d585469d6e31f31dc49be96f
|
||||
CapacitorDialog: 9b934329026b2b0ffa56939bb06df3c67541a2ab
|
||||
CapacitorHaptics: 70e47470fa1a6bd6338cd102552e3846b7f9a1b3
|
||||
CapacitorNetwork: 07ec4c69c1bb696f41c23e00d31bda1bbb221bba
|
||||
CapacitorPreferences: cbf154e5e5519b7f5ab33817a334dda1e98387f9
|
||||
CapacitorStatusBar: 275cbf2f4dfc00388f519ef80c7ec22edda342c9
|
||||
CordovaPlugins: 5a72a85b45469e68556bb172409f1b6d57b27236
|
||||
Realm: 8b5cda39a41f17a1734da2f39c6004eb8745587a
|
||||
RealmSwift: 0b4f808fed6898f1f6c26f501f740efd80dff0b4
|
||||
WebnativellcCapacitorFilesharer: 10b111373d4dc49608935600dcbcc14605258c73
|
||||
CapacitorDialog: 0e09f242f6c3f5e82e4dc76b20f2a056be57a579
|
||||
CapacitorHaptics: 1f1e17041f435d8ead9ff2a34edd592c6aa6a8d6
|
||||
CapacitorNetwork: 15cb4385f0913a8ceb5e9a4d7af1ec554bdb8de8
|
||||
CapacitorPreferences: 6c98117d4d7508034a4af9db64d6b26fc75d7b94
|
||||
CapacitorStatusBar: 6e7af040d8fc4dd655999819625cae9c2d74c36f
|
||||
CordovaPlugins: 2ecbba09775516c41764dbf78ade612427311b7e
|
||||
Realm: b1b3bc68162fa242132eb7eefbf91d7c40f36a85
|
||||
RealmSwift: 456cfd82a4f23dff8e3456980999331ab69bbf3e
|
||||
WebnativellcCapacitorFilesharer: e3a5930240633db3335040251d66aac6762ff111
|
||||
|
||||
PODFILE CHECKSUM: 498821c0cfa2508609567fa95d7244c01cbef538
|
||||
|
||||
|
||||
Generated
+2
-2
@@ -1,12 +1,12 @@
|
||||
{
|
||||
"name": "audiobookshelf-app",
|
||||
"version": "0.12.0-beta",
|
||||
"version": "0.13.0-beta",
|
||||
"lockfileVersion": 3,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "audiobookshelf-app",
|
||||
"version": "0.12.0-beta",
|
||||
"version": "0.13.0-beta",
|
||||
"dependencies": {
|
||||
"@capacitor-community/keep-awake": "^7.0.0",
|
||||
"@capacitor-community/volume-buttons": "^7.0.0",
|
||||
|
||||
+1
-1
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "audiobookshelf-app",
|
||||
"version": "0.12.0-beta",
|
||||
"version": "0.13.0-beta",
|
||||
"author": "advplyr",
|
||||
"scripts": {
|
||||
"dev": "nuxt --hostname 0.0.0.0 --port 1337",
|
||||
|
||||
@@ -13,7 +13,7 @@
|
||||
</div>
|
||||
</div>
|
||||
<div class="relative" @click="showFullscreenCover = true">
|
||||
<covers-book-cover :library-item="libraryItem" :width="coverWidth" :book-cover-aspect-ratio="bookCoverAspectRatio" no-bg raw @imageLoaded="coverImageLoaded" />
|
||||
<covers-book-cover :library-item="libraryItem" :width="coverWidth" :book-cover-aspect-ratio="bookCoverAspectRatio" no-bg raw />
|
||||
<div v-if="!isPodcast" class="absolute bottom-0 left-0 h-1 z-10 box-shadow-progressbar" :class="userIsFinished ? 'bg-success' : 'bg-yellow-400'" :style="{ width: coverWidth * progressPercent + 'px' }"></div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -172,7 +172,7 @@
|
||||
<script>
|
||||
import { Dialog } from '@capacitor/dialog'
|
||||
import { AbsFileSystem, AbsDownloader } from '@/plugins/capacitor'
|
||||
import { FastAverageColor } from 'fast-average-color'
|
||||
import { getAverageColorFromCoverUrl } from '@/utils/coverAverageColor'
|
||||
import cellularPermissionHelpers from '@/mixins/cellularPermissionHelpers'
|
||||
|
||||
export default {
|
||||
@@ -496,17 +496,10 @@ export default {
|
||||
},
|
||||
async coverImageLoaded(fullCoverUrl) {
|
||||
if (!fullCoverUrl) return
|
||||
|
||||
const fac = new FastAverageColor()
|
||||
fac
|
||||
.getColorAsync(fullCoverUrl)
|
||||
.then((color) => {
|
||||
this.coverRgb = color.rgba
|
||||
this.coverBgIsLight = color.isLight
|
||||
})
|
||||
.catch((e) => {
|
||||
console.log(e)
|
||||
})
|
||||
const avg = await getAverageColorFromCoverUrl(this, fullCoverUrl)
|
||||
if (!avg) return
|
||||
this.coverRgb = avg.rgba
|
||||
this.coverBgIsLight = avg.isLight
|
||||
},
|
||||
moreButtonPress() {
|
||||
this.showMoreMenu = true
|
||||
|
||||
@@ -51,7 +51,7 @@ class AbsDatabaseWeb extends WebPlugin {
|
||||
ssc.customHeaders = serverConnectionConfig.customHeaders || {}
|
||||
|
||||
if (serverConnectionConfig.refreshToken) {
|
||||
console.log('[AbsDatabase] Updating refresh token...', serverConnectionConfig.refreshToken)
|
||||
console.log('[AbsDatabase] Updating refresh token...')
|
||||
// Only using local storage for web version that is only used for testing
|
||||
localStorage.setItem(`refresh_token_${ssc.id}`, serverConnectionConfig.refreshToken)
|
||||
}
|
||||
@@ -71,7 +71,7 @@ class AbsDatabaseWeb extends WebPlugin {
|
||||
}
|
||||
|
||||
if (serverConnectionConfig.refreshToken) {
|
||||
console.log('[AbsDatabase] Setting refresh token...', serverConnectionConfig.refreshToken)
|
||||
console.log('[AbsDatabase] Setting refresh token...')
|
||||
// Only using local storage for web version that is only used for testing
|
||||
localStorage.setItem(`refresh_token_${ssc.id}`, serverConnectionConfig.refreshToken)
|
||||
}
|
||||
|
||||
+1
-2
@@ -128,13 +128,12 @@ export default ({ app, store }, inject) => {
|
||||
|
||||
// Listen for token refresh events from native app
|
||||
AbsDatabase.addListener('onTokenRefresh', (data) => {
|
||||
console.log('[db] onTokenRefresh', data)
|
||||
store.commit('user/setAccessToken', data.accessToken)
|
||||
})
|
||||
|
||||
// Listen for token refresh failure events from native app
|
||||
AbsDatabase.addListener('onTokenRefreshFailure', async (data) => {
|
||||
console.log('[db] onTokenRefreshFailure', data)
|
||||
console.log('[db] onTokenRefreshFailure')
|
||||
// Clear store and redirect to login page
|
||||
await store.dispatch('user/logout')
|
||||
if (window.location.pathname !== '/connect') {
|
||||
|
||||
@@ -43,7 +43,8 @@ export default function ({ store, $db, $socket }, inject) {
|
||||
}
|
||||
if (res.status >= 400) {
|
||||
console.error(`[nativeHttp] ${res.status} status for url "${url}"`)
|
||||
throw new Error(res.data)
|
||||
const message = typeof res.data === 'string' ? res.data : `HTTP ${res.status}`
|
||||
throw new Error(message)
|
||||
}
|
||||
return res.data
|
||||
})
|
||||
@@ -100,7 +101,8 @@ export default function ({ store, $db, $socket }, inject) {
|
||||
|
||||
if (retryResponse.status >= 400) {
|
||||
console.error(`[nativeHttp] Retry request failed with status ${retryResponse.status}`)
|
||||
throw new Error(retryResponse.data)
|
||||
const message = typeof retryResponse.data === 'string' ? retryResponse.data : `HTTP ${retryResponse.status}`
|
||||
throw new Error(message)
|
||||
}
|
||||
|
||||
return retryResponse.data
|
||||
|
||||
@@ -231,7 +231,6 @@ export const mutations = {
|
||||
state.user = user
|
||||
},
|
||||
setAccessToken(state, accessToken) {
|
||||
console.log('[user] setAccessToken', accessToken)
|
||||
state.accessToken = accessToken
|
||||
},
|
||||
removeMediaProgress(state, id) {
|
||||
|
||||
@@ -0,0 +1,59 @@
|
||||
import { Capacitor } from '@capacitor/core'
|
||||
import { FastAverageColor } from 'fast-average-color'
|
||||
import { imageHttpDataToBlob } from '@/utils/imageHttpBlob'
|
||||
|
||||
/**
|
||||
* True when the cover URL is http(s) and not same-origin with the WebView (or browser tab).
|
||||
* Same-origin URLs (e.g. Capacitor file bridge on localhost) can use FastAverageColor directly.
|
||||
*/
|
||||
export function shouldFetchCoverViaNativeHttp(coverUrl) {
|
||||
if (!coverUrl || typeof coverUrl !== 'string') return false
|
||||
if (!coverUrl.startsWith('http://') && !coverUrl.startsWith('https://')) return false
|
||||
try {
|
||||
return new URL(coverUrl).origin !== window.location.origin
|
||||
} catch {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Average color for a cover image. On native, cross-origin http(s) covers are loaded with
|
||||
* CapacitorHttp (no WebView CORS), then sampled via a same-origin blob URL.
|
||||
* @param {*} vm - component instance with $nativeHttp (this)
|
||||
* @param {string} fullCoverUrl
|
||||
* @returns {Promise<{ rgba: string, isLight: boolean }|null>}
|
||||
*/
|
||||
export async function getAverageColorFromCoverUrl(vm, fullCoverUrl) {
|
||||
if (!fullCoverUrl) return null
|
||||
|
||||
const fac = new FastAverageColor()
|
||||
let objectUrl = null
|
||||
try {
|
||||
let resource = fullCoverUrl
|
||||
|
||||
if (Capacitor.isNativePlatform() && shouldFetchCoverViaNativeHttp(fullCoverUrl)) {
|
||||
const raw = await vm.$nativeHttp.get(fullCoverUrl, {
|
||||
responseType: 'blob',
|
||||
connectTimeout: 15000,
|
||||
readTimeout: 30000
|
||||
})
|
||||
const blob = imageHttpDataToBlob(raw)
|
||||
if (!blob) {
|
||||
throw new Error('Cover image response could not be converted to a blob')
|
||||
}
|
||||
objectUrl = URL.createObjectURL(blob)
|
||||
resource = objectUrl
|
||||
}
|
||||
|
||||
const color = await fac.getColorAsync(resource)
|
||||
return { rgba: color.rgba, isLight: color.isLight }
|
||||
} catch (e) {
|
||||
console.error('[coverAverageColor]', e)
|
||||
return null
|
||||
} finally {
|
||||
if (objectUrl) {
|
||||
URL.revokeObjectURL(objectUrl)
|
||||
}
|
||||
fac.destroy()
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,40 @@
|
||||
/**
|
||||
* Normalize CapacitorHttp binary responses (Blob, base64 string, data URL, ArrayBuffer) to a Blob.
|
||||
* @param {unknown} data - CapacitorHttp response `data` when responseType is blob/arraybuffer
|
||||
* @param {string} [mimeType='image/jpeg'] - fallback Content-Type
|
||||
* @returns {Blob|null}
|
||||
*/
|
||||
export function imageHttpDataToBlob(data, mimeType = 'image/jpeg') {
|
||||
if (data == null) return null
|
||||
if (typeof Blob !== 'undefined' && data instanceof Blob) return data
|
||||
if (data instanceof ArrayBuffer) return new Blob([data], { type: mimeType })
|
||||
if (ArrayBuffer.isView(data)) return new Blob([data], { type: mimeType })
|
||||
if (typeof data === 'object' && typeof data.base64 === 'string') {
|
||||
return base64ToBlob(data.base64, mimeType)
|
||||
}
|
||||
if (typeof data === 'string') {
|
||||
if (data.startsWith('data:')) {
|
||||
const match = /^data:([^;]+);base64,([\s\S]+)$/.exec(data)
|
||||
if (match) {
|
||||
const type = match[1] || mimeType
|
||||
return base64ToBlob(match[2], type)
|
||||
}
|
||||
}
|
||||
try {
|
||||
return base64ToBlob(data, mimeType)
|
||||
} catch {
|
||||
return null
|
||||
}
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
||||
function base64ToBlob(base64, type) {
|
||||
const binaryString = atob(base64)
|
||||
const len = binaryString.length
|
||||
const bytes = new Uint8Array(len)
|
||||
for (let i = 0; i < len; i++) {
|
||||
bytes[i] = binaryString.charCodeAt(i)
|
||||
}
|
||||
return new Blob([bytes], { type })
|
||||
}
|
||||
Reference in New Issue
Block a user