mirror of
https://github.com/advplyr/audiobookshelf-app.git
synced 2026-08-09 05:18:40 +02:00
Compare commits
34
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
00bed39733 | ||
|
|
2048795c92 | ||
|
|
8c9bd53934 | ||
|
|
01178d00bf | ||
|
|
7541e4b660 | ||
|
|
2366c938b5 | ||
|
|
59480ad114 | ||
|
|
ce7779607f | ||
|
|
04bc74babe | ||
|
|
ac2b674ba5 | ||
|
|
e53901e7a4 | ||
|
|
3ede41f496 | ||
|
|
0adeaaa04d | ||
|
|
57d1fbfa83 | ||
|
|
27bbe40874 | ||
|
|
60da6093f3 | ||
|
|
259c9cef20 | ||
|
|
6d61b2acaf | ||
|
|
72f687b079 | ||
|
|
0ce3c8bcbd | ||
|
|
f9621d2f60 | ||
|
|
e634132a39 | ||
|
|
59b428100b | ||
|
|
36e0c91e27 | ||
|
|
03225957aa | ||
|
|
77f70e48de | ||
|
|
cbdc3513c7 | ||
|
|
394479bea0 | ||
|
|
b9592e46a7 | ||
|
|
5810a83c66 | ||
|
|
389c7d23ca | ||
|
|
defc7d216a | ||
|
|
852702112a | ||
|
|
dd91bc6667 |
@@ -21,6 +21,12 @@ jobs:
|
|||||||
with:
|
with:
|
||||||
node-version: 16
|
node-version: 16
|
||||||
|
|
||||||
|
- name: Set up Java
|
||||||
|
uses: actions/setup-java@v2
|
||||||
|
with:
|
||||||
|
distribution: "temurin"
|
||||||
|
java-version: 17
|
||||||
|
|
||||||
- name: install dependencies
|
- name: install dependencies
|
||||||
run: npm ci
|
run: npm ci
|
||||||
|
|
||||||
|
|||||||
@@ -20,6 +20,12 @@ jobs:
|
|||||||
with:
|
with:
|
||||||
node-version: 16
|
node-version: 16
|
||||||
|
|
||||||
|
- name: Set up Java
|
||||||
|
uses: actions/setup-java@v2
|
||||||
|
with:
|
||||||
|
distribution: "temurin"
|
||||||
|
java-version: 17
|
||||||
|
|
||||||
- name: install dependencies
|
- name: install dependencies
|
||||||
run: npm ci
|
run: npm ci
|
||||||
|
|
||||||
|
|||||||
@@ -33,8 +33,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 96
|
versionCode 97
|
||||||
versionName "0.9.65-beta"
|
versionName "0.9.66-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.
|
||||||
@@ -63,12 +63,11 @@ repositories {
|
|||||||
mavenCentral()
|
mavenCentral()
|
||||||
}
|
}
|
||||||
|
|
||||||
configurations.all {
|
configurations.configureEach {
|
||||||
resolutionStrategy {
|
resolutionStrategy {
|
||||||
force("com.google.android.gms:play-services-base:18.0.1")
|
force("com.google.android.gms:play-services-base:18.0.1")
|
||||||
force("androidx.appcompat:appcompat:$androidxAppCompatVersion")
|
force("androidx.appcompat:appcompat:$androidxAppCompatVersion")
|
||||||
force("androidx.core:core-ktx:$androidx_core_ktx_version")
|
force("androidx.core:core-ktx:$androidx_core_ktx_version")
|
||||||
force("org.jetbrains.kotlin:kotlin-stdlib-jdk8:$kotlin_version")
|
|
||||||
force("androidx.media:media:$androidx_media_version")
|
force("androidx.media:media:$androidx_media_version")
|
||||||
force("org.jetbrains.kotlinx:kotlinx-coroutines-core:$kotlin_coroutines_version")
|
force("org.jetbrains.kotlinx:kotlinx-coroutines-core:$kotlin_coroutines_version")
|
||||||
force("org.jetbrains.kotlinx:kotlinx-coroutines-android:$kotlin_coroutines_version")
|
force("org.jetbrains.kotlinx:kotlinx-coroutines-android:$kotlin_coroutines_version")
|
||||||
@@ -81,7 +80,7 @@ dependencies {
|
|||||||
implementation "androidx.appcompat:appcompat:$androidxAppCompatVersion"
|
implementation "androidx.appcompat:appcompat:$androidxAppCompatVersion"
|
||||||
implementation project(':capacitor-android')
|
implementation project(':capacitor-android')
|
||||||
|
|
||||||
implementation 'androidx.constraintlayout:constraintlayout:2.1.3'
|
implementation 'androidx.constraintlayout:constraintlayout:2.1.4'
|
||||||
implementation "androidx.coordinatorlayout:coordinatorlayout:$androidxCoordinatorLayoutVersion"
|
implementation "androidx.coordinatorlayout:coordinatorlayout:$androidxCoordinatorLayoutVersion"
|
||||||
|
|
||||||
implementation project(':capacitor-cordova-android-plugins')
|
implementation project(':capacitor-cordova-android-plugins')
|
||||||
@@ -111,7 +110,6 @@ dependencies {
|
|||||||
|
|
||||||
// Glide for images
|
// Glide for images
|
||||||
implementation "com.github.bumptech.glide:glide:$glide_version"
|
implementation "com.github.bumptech.glide:glide:$glide_version"
|
||||||
kapt "com.github.bumptech.glide:compiler:$glide_version"
|
|
||||||
|
|
||||||
// Paper NoSQL Db
|
// Paper NoSQL Db
|
||||||
implementation 'io.github.pilgr:paperdb:2.7.2'
|
implementation 'io.github.pilgr:paperdb:2.7.2'
|
||||||
|
|||||||
@@ -2,5 +2,10 @@
|
|||||||
"appId": "com.audiobookshelf.app",
|
"appId": "com.audiobookshelf.app",
|
||||||
"appName": "audiobookshelf-app",
|
"appName": "audiobookshelf-app",
|
||||||
"webDir": "dist",
|
"webDir": "dist",
|
||||||
"bundledWebRuntime": false
|
"bundledWebRuntime": false,
|
||||||
|
"plugins": {
|
||||||
|
"CapacitorHttp": {
|
||||||
|
"enabled": false
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -95,7 +95,7 @@ internal fun updateAppWidget(context: Context, appWidgetManager: AppWidgetManage
|
|||||||
|
|
||||||
Log.i(tag, "Update App Widget | Is Playing=$isPlaying | isAppClosed=$isAppClosed")
|
Log.i(tag, "Update App Widget | Is Playing=$isPlaying | isAppClosed=$isAppClosed")
|
||||||
|
|
||||||
val playPauseResource = if (isPlaying) R.drawable.ic_media_pause_dark else R.drawable.ic_media_play_dark
|
val playPauseResource = if (isPlaying) androidx.mediarouter.R.drawable.ic_media_pause_dark else androidx.mediarouter.R.drawable.ic_media_play_dark
|
||||||
views.setImageViewResource(R.id.widgetPlayPauseButton, playPauseResource)
|
views.setImageViewResource(R.id.widgetPlayPauseButton, playPauseResource)
|
||||||
|
|
||||||
// Instruct the widget manager to update the widget
|
// Instruct the widget manager to update the widget
|
||||||
|
|||||||
@@ -24,7 +24,7 @@ open class MediaType(var metadata:MediaTypeMetadata, var coverPath:String?) {
|
|||||||
@JsonIgnore
|
@JsonIgnore
|
||||||
open fun removeAudioTrack(localFileId:String) { }
|
open fun removeAudioTrack(localFileId:String) { }
|
||||||
@JsonIgnore
|
@JsonIgnore
|
||||||
open fun getLocalCopy():MediaType { return MediaType(MediaTypeMetadata(""),null) }
|
open fun getLocalCopy():MediaType { return MediaType(MediaTypeMetadata("", false),null) }
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -190,7 +190,7 @@ class Book(
|
|||||||
JsonSubTypes.Type(BookMetadata::class),
|
JsonSubTypes.Type(BookMetadata::class),
|
||||||
JsonSubTypes.Type(PodcastMetadata::class)
|
JsonSubTypes.Type(PodcastMetadata::class)
|
||||||
)
|
)
|
||||||
open class MediaTypeMetadata(var title:String) {
|
open class MediaTypeMetadata(var title:String, var explicit:Boolean) {
|
||||||
@JsonIgnore
|
@JsonIgnore
|
||||||
open fun getAuthorDisplayName():String { return "Unknown" }
|
open fun getAuthorDisplayName():String { return "Unknown" }
|
||||||
}
|
}
|
||||||
@@ -209,13 +209,13 @@ class BookMetadata(
|
|||||||
var isbn:String?,
|
var isbn:String?,
|
||||||
var asin:String?,
|
var asin:String?,
|
||||||
var language:String?,
|
var language:String?,
|
||||||
var explicit:Boolean,
|
explicit:Boolean,
|
||||||
// In toJSONExpanded
|
// In toJSONExpanded
|
||||||
var authorName:String?,
|
var authorName:String?,
|
||||||
var authorNameLF:String?,
|
var authorNameLF:String?,
|
||||||
var narratorName:String?,
|
var narratorName:String?,
|
||||||
var seriesName:String?
|
var seriesName:String?
|
||||||
) : MediaTypeMetadata(title) {
|
) : MediaTypeMetadata(title, explicit) {
|
||||||
@JsonIgnore
|
@JsonIgnore
|
||||||
override fun getAuthorDisplayName():String { return authorName ?: "Unknown" }
|
override fun getAuthorDisplayName():String { return authorName ?: "Unknown" }
|
||||||
}
|
}
|
||||||
@@ -225,8 +225,9 @@ class PodcastMetadata(
|
|||||||
title:String,
|
title:String,
|
||||||
var author:String?,
|
var author:String?,
|
||||||
var feedUrl:String?,
|
var feedUrl:String?,
|
||||||
var genres:MutableList<String>
|
var genres:MutableList<String>,
|
||||||
) : MediaTypeMetadata(title) {
|
explicit:Boolean
|
||||||
|
) : MediaTypeMetadata(title, explicit) {
|
||||||
@JsonIgnore
|
@JsonIgnore
|
||||||
override fun getAuthorDisplayName():String { return author ?: "Unknown" }
|
override fun getAuthorDisplayName():String { return author ?: "Unknown" }
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -90,6 +90,10 @@ class LibraryItem(
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (media.metadata.explicit) {
|
||||||
|
extras.putLong(MediaConstants.METADATA_KEY_IS_EXPLICIT, MediaConstants.METADATA_VALUE_ATTRIBUTE_PRESENT)
|
||||||
|
}
|
||||||
|
|
||||||
val mediaId = localLibraryItemId ?: id
|
val mediaId = localLibraryItemId ?: id
|
||||||
return MediaDescriptionCompat.Builder()
|
return MediaDescriptionCompat.Builder()
|
||||||
.setMediaId(mediaId)
|
.setMediaId(mediaId)
|
||||||
|
|||||||
@@ -152,6 +152,10 @@ class LocalLibraryItem(
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (media.metadata.explicit) {
|
||||||
|
extras.putLong(MediaConstants.METADATA_KEY_IS_EXPLICIT, MediaConstants.METADATA_VALUE_ATTRIBUTE_PRESENT)
|
||||||
|
}
|
||||||
|
|
||||||
val mediaDescriptionBuilder = MediaDescriptionCompat.Builder()
|
val mediaDescriptionBuilder = MediaDescriptionCompat.Builder()
|
||||||
.setMediaId(id)
|
.setMediaId(id)
|
||||||
.setTitle(title)
|
.setTitle(title)
|
||||||
|
|||||||
@@ -43,7 +43,7 @@ data class LocalMediaItem(
|
|||||||
return if (mediaType == "book") {
|
return if (mediaType == "book") {
|
||||||
BookMetadata(name,null, mutableListOf(), mutableListOf(), mutableListOf(),null,null,null,null,null,null,null,false,null,null,null,null)
|
BookMetadata(name,null, mutableListOf(), mutableListOf(), mutableListOf(),null,null,null,null,null,null,null,false,null,null,null,null)
|
||||||
} else {
|
} else {
|
||||||
PodcastMetadata(name,null,null, mutableListOf())
|
PodcastMetadata(name,null,null, mutableListOf(), false)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -90,6 +90,7 @@ class DownloadItemManager(var downloadManager:DownloadManager, private var folde
|
|||||||
|
|
||||||
Log.d(tag, "Start internal download to destination path ${it.finalDestinationPath} from ${it.serverUrl}")
|
Log.d(tag, "Start internal download to destination path ${it.finalDestinationPath} from ${it.serverUrl}")
|
||||||
InternalDownloadManager(fileOutputStream, internalProgressCallback).download(it.serverUrl)
|
InternalDownloadManager(fileOutputStream, internalProgressCallback).download(it.serverUrl)
|
||||||
|
it.downloadId = 1
|
||||||
currentDownloadItemParts.add(it)
|
currentDownloadItemParts.add(it)
|
||||||
} else {
|
} else {
|
||||||
val dlRequest = it.getDownloadRequest()
|
val dlRequest = it.getDownloadRequest()
|
||||||
|
|||||||
+44
-42
@@ -25,7 +25,6 @@ import androidx.core.app.NotificationCompat
|
|||||||
import androidx.core.content.ContextCompat
|
import androidx.core.content.ContextCompat
|
||||||
import androidx.media.MediaBrowserServiceCompat
|
import androidx.media.MediaBrowserServiceCompat
|
||||||
import androidx.media.utils.MediaConstants
|
import androidx.media.utils.MediaConstants
|
||||||
import com.audiobookshelf.app.BuildConfig
|
|
||||||
import com.audiobookshelf.app.R
|
import com.audiobookshelf.app.R
|
||||||
import com.audiobookshelf.app.data.*
|
import com.audiobookshelf.app.data.*
|
||||||
import com.audiobookshelf.app.data.DeviceInfo
|
import com.audiobookshelf.app.data.DeviceInfo
|
||||||
@@ -65,7 +64,7 @@ class PlayerNotificationService : MediaBrowserServiceCompat() {
|
|||||||
var isSwitchingPlayer = false // Used when switching between cast player and exoplayer
|
var isSwitchingPlayer = false // Used when switching between cast player and exoplayer
|
||||||
}
|
}
|
||||||
|
|
||||||
private val tag = "PlayerNotificationService"
|
private val tag = "PlayerNotificationServ"
|
||||||
|
|
||||||
interface ClientEventEmitter {
|
interface ClientEventEmitter {
|
||||||
fun onPlaybackSession(playbackSession:PlaybackSession)
|
fun onPlaybackSession(playbackSession:PlaybackSession)
|
||||||
@@ -938,7 +937,7 @@ class PlayerNotificationService : MediaBrowserServiceCompat() {
|
|||||||
appVersion: 0.9.46-beta
|
appVersion: 0.9.46-beta
|
||||||
*/
|
*/
|
||||||
val deviceId = Settings.Secure.getString(ctx.contentResolver, Settings.Secure.ANDROID_ID)
|
val deviceId = Settings.Secure.getString(ctx.contentResolver, Settings.Secure.ANDROID_ID)
|
||||||
return DeviceInfo(deviceId, Build.MANUFACTURER, Build.MODEL, Build.VERSION.SDK_INT, BuildConfig.VERSION_NAME)
|
return DeviceInfo(deviceId, Build.MANUFACTURER, Build.MODEL, Build.VERSION.SDK_INT, Build.VERSION.RELEASE)
|
||||||
}
|
}
|
||||||
|
|
||||||
private val deviceSettings get() = DeviceManager.deviceData.deviceSettings ?: DeviceSettings.default()
|
private val deviceSettings get() = DeviceManager.deviceData.deviceSettings ?: DeviceSettings.default()
|
||||||
@@ -995,13 +994,16 @@ class PlayerNotificationService : MediaBrowserServiceCompat() {
|
|||||||
|
|
||||||
val extras = Bundle()
|
val extras = Bundle()
|
||||||
extras.putBoolean(
|
extras.putBoolean(
|
||||||
MediaConstants.BROWSER_SERVICE_EXTRAS_KEY_SEARCH_SUPPORTED, true)
|
MediaConstants.BROWSER_SERVICE_EXTRAS_KEY_SEARCH_SUPPORTED, true
|
||||||
|
)
|
||||||
extras.putInt(
|
extras.putInt(
|
||||||
MediaConstants.DESCRIPTION_EXTRAS_KEY_CONTENT_STYLE_BROWSABLE,
|
MediaConstants.DESCRIPTION_EXTRAS_KEY_CONTENT_STYLE_BROWSABLE,
|
||||||
MediaConstants.DESCRIPTION_EXTRAS_VALUE_CONTENT_STYLE_LIST_ITEM)
|
MediaConstants.DESCRIPTION_EXTRAS_VALUE_CONTENT_STYLE_LIST_ITEM
|
||||||
|
)
|
||||||
extras.putInt(
|
extras.putInt(
|
||||||
MediaConstants.DESCRIPTION_EXTRAS_KEY_CONTENT_STYLE_PLAYABLE,
|
MediaConstants.DESCRIPTION_EXTRAS_KEY_CONTENT_STYLE_PLAYABLE,
|
||||||
MediaConstants.DESCRIPTION_EXTRAS_VALUE_CONTENT_STYLE_LIST_ITEM)
|
MediaConstants.DESCRIPTION_EXTRAS_VALUE_CONTENT_STYLE_LIST_ITEM
|
||||||
|
)
|
||||||
|
|
||||||
BrowserRoot(AUTO_MEDIA_ROOT, extras)
|
BrowserRoot(AUTO_MEDIA_ROOT, extras)
|
||||||
}
|
}
|
||||||
@@ -1012,29 +1014,7 @@ class PlayerNotificationService : MediaBrowserServiceCompat() {
|
|||||||
|
|
||||||
result.detach()
|
result.detach()
|
||||||
|
|
||||||
if (parentMediaId.startsWith("li_") || parentMediaId.startsWith("local_")) { // Show podcast episodes
|
if (parentMediaId == DOWNLOADS_ROOT) { // Load downloads
|
||||||
Log.d(tag, "Loading podcast episodes")
|
|
||||||
mediaManager.loadPodcastEpisodeMediaBrowserItems(parentMediaId, ctx) {
|
|
||||||
result.sendResult(it)
|
|
||||||
}
|
|
||||||
} else if (::browseTree.isInitialized && browseTree[parentMediaId] == null && mediaManager.getIsLibrary(parentMediaId)) { // Load library items for library
|
|
||||||
|
|
||||||
mediaManager.loadLibraryItemsWithAudio(parentMediaId) { libraryItems ->
|
|
||||||
val children = libraryItems.map { libraryItem ->
|
|
||||||
if (libraryItem.mediaType == "podcast") { // Podcasts are browseable
|
|
||||||
val mediaDescription = libraryItem.getMediaDescription(null, ctx)
|
|
||||||
MediaBrowserCompat.MediaItem(mediaDescription, MediaBrowserCompat.MediaItem.FLAG_BROWSABLE)
|
|
||||||
} else {
|
|
||||||
val progress = mediaManager.serverUserMediaProgress.find { it.libraryItemId == libraryItem.id }
|
|
||||||
val localLibraryItem = DeviceManager.dbManager.getLocalLibraryItemByLId(libraryItem.id)
|
|
||||||
libraryItem.localLibraryItemId = localLibraryItem?.id
|
|
||||||
val description = libraryItem.getMediaDescription(progress, ctx)
|
|
||||||
MediaBrowserCompat.MediaItem(description, MediaBrowserCompat.MediaItem.FLAG_PLAYABLE)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
result.sendResult(children as MutableList<MediaBrowserCompat.MediaItem>?)
|
|
||||||
}
|
|
||||||
} else if (parentMediaId == DOWNLOADS_ROOT) { // Load downloads
|
|
||||||
|
|
||||||
val localBooks = DeviceManager.dbManager.getLocalLibraryItems("book")
|
val localBooks = DeviceManager.dbManager.getLocalLibraryItems("book")
|
||||||
val localPodcasts = DeviceManager.dbManager.getLocalLibraryItems("podcast")
|
val localPodcasts = DeviceManager.dbManager.getLocalLibraryItems("podcast")
|
||||||
@@ -1062,19 +1042,19 @@ class PlayerNotificationService : MediaBrowserServiceCompat() {
|
|||||||
val progress: MediaProgressWrapper?
|
val progress: MediaProgressWrapper?
|
||||||
val mediaDescription:MediaDescriptionCompat
|
val mediaDescription:MediaDescriptionCompat
|
||||||
if (itemInProgress.episode != null) {
|
if (itemInProgress.episode != null) {
|
||||||
if (itemInProgress.isLocal) {
|
if (itemInProgress.isLocal) {
|
||||||
progress = DeviceManager.dbManager.getLocalMediaProgress("${itemInProgress.libraryItemWrapper.id}-${itemInProgress.episode.id}")
|
progress = DeviceManager.dbManager.getLocalMediaProgress("${itemInProgress.libraryItemWrapper.id}-${itemInProgress.episode.id}")
|
||||||
} else {
|
} else {
|
||||||
progress = mediaManager.serverUserMediaProgress.find { it.libraryItemId == itemInProgress.libraryItemWrapper.id && it.episodeId == itemInProgress.episode.id }
|
progress = mediaManager.serverUserMediaProgress.find { it.libraryItemId == itemInProgress.libraryItemWrapper.id && it.episodeId == itemInProgress.episode.id }
|
||||||
|
|
||||||
// to show download icon
|
// to show download icon
|
||||||
val localLibraryItem = DeviceManager.dbManager.getLocalLibraryItemByLId(itemInProgress.libraryItemWrapper.id)
|
val localLibraryItem = DeviceManager.dbManager.getLocalLibraryItemByLId(itemInProgress.libraryItemWrapper.id)
|
||||||
localLibraryItem?.let { lli ->
|
localLibraryItem?.let { lli ->
|
||||||
val localEpisode = (lli.media as Podcast).episodes?.find { it.serverEpisodeId == itemInProgress.episode.id }
|
val localEpisode = (lli.media as Podcast).episodes?.find { it.serverEpisodeId == itemInProgress.episode.id }
|
||||||
itemInProgress.episode.localEpisodeId = localEpisode?.id
|
itemInProgress.episode.localEpisodeId = localEpisode?.id
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
mediaDescription = itemInProgress.episode.getMediaDescription(itemInProgress.libraryItemWrapper, progress, ctx)
|
mediaDescription = itemInProgress.episode.getMediaDescription(itemInProgress.libraryItemWrapper, progress, ctx)
|
||||||
} else {
|
} else {
|
||||||
if (itemInProgress.isLocal) {
|
if (itemInProgress.isLocal) {
|
||||||
@@ -1090,16 +1070,38 @@ class PlayerNotificationService : MediaBrowserServiceCompat() {
|
|||||||
localBrowseItems += MediaBrowserCompat.MediaItem(mediaDescription, MediaBrowserCompat.MediaItem.FLAG_PLAYABLE)
|
localBrowseItems += MediaBrowserCompat.MediaItem(mediaDescription, MediaBrowserCompat.MediaItem.FLAG_PLAYABLE)
|
||||||
}
|
}
|
||||||
result.sendResult(localBrowseItems)
|
result.sendResult(localBrowseItems)
|
||||||
} else {
|
} else if (parentMediaId == LIBRARIES_ROOT || parentMediaId == AUTO_MEDIA_ROOT) {
|
||||||
mediaManager.loadAndroidAutoItems {
|
mediaManager.loadAndroidAutoItems {
|
||||||
browseTree = BrowseTree(this, mediaManager.serverItemsInProgress, mediaManager.serverLibraries)
|
browseTree = BrowseTree(this, mediaManager.serverItemsInProgress, mediaManager.serverLibraries)
|
||||||
|
|
||||||
val children = browseTree[parentMediaId]?.map { item ->
|
val children = browseTree[parentMediaId]?.map { item ->
|
||||||
Log.d(tag, "Loading Browser Media Item ${item.description.title}")
|
Log.d(tag, "Loading Browser Media Item ${item.description.title}")
|
||||||
MediaBrowserCompat.MediaItem(item.description, MediaBrowserCompat.MediaItem.FLAG_BROWSABLE)
|
MediaBrowserCompat.MediaItem(item.description, MediaBrowserCompat.MediaItem.FLAG_BROWSABLE)
|
||||||
}
|
}
|
||||||
result.sendResult(children as MutableList<MediaBrowserCompat.MediaItem>?)
|
result.sendResult(children as MutableList<MediaBrowserCompat.MediaItem>?)
|
||||||
}
|
}
|
||||||
|
} else if (mediaManager.getIsLibrary(parentMediaId)) { // Load library items for library
|
||||||
|
Log.d(tag, "Loading items for library $parentMediaId")
|
||||||
|
mediaManager.loadLibraryItemsWithAudio(parentMediaId) { libraryItems ->
|
||||||
|
val children = libraryItems.map { libraryItem ->
|
||||||
|
if (libraryItem.mediaType == "podcast") { // Podcasts are browseable
|
||||||
|
val mediaDescription = libraryItem.getMediaDescription(null, ctx)
|
||||||
|
MediaBrowserCompat.MediaItem(mediaDescription, MediaBrowserCompat.MediaItem.FLAG_BROWSABLE)
|
||||||
|
} else {
|
||||||
|
val progress = mediaManager.serverUserMediaProgress.find { it.libraryItemId == libraryItem.id }
|
||||||
|
val localLibraryItem = DeviceManager.dbManager.getLocalLibraryItemByLId(libraryItem.id)
|
||||||
|
libraryItem.localLibraryItemId = localLibraryItem?.id
|
||||||
|
val description = libraryItem.getMediaDescription(progress, ctx)
|
||||||
|
MediaBrowserCompat.MediaItem(description, MediaBrowserCompat.MediaItem.FLAG_PLAYABLE)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
result.sendResult(children as MutableList<MediaBrowserCompat.MediaItem>?)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
Log.d(tag, "Loading podcast episodes for podcast $parentMediaId")
|
||||||
|
mediaManager.loadPodcastEpisodeMediaBrowserItems(parentMediaId, ctx) {
|
||||||
|
result.sendResult(it)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -231,6 +231,10 @@ class AbsDatabase : Plugin() {
|
|||||||
if (!success) {
|
if (!success) {
|
||||||
call.resolve(JSObject("{\"error\":\"$errorMsg\"}"))
|
call.resolve(JSObject("{\"error\":\"$errorMsg\"}"))
|
||||||
} else {
|
} else {
|
||||||
|
// Remove all local sessions
|
||||||
|
savedSessions.forEach {
|
||||||
|
DeviceManager.dbManager.removePlaybackSession(it.id)
|
||||||
|
}
|
||||||
call.resolve()
|
call.resolve()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -323,13 +323,15 @@ class ApiHandler(var ctx:Context) {
|
|||||||
|
|
||||||
fun sendSyncLocalSessions(playbackSessions:List<PlaybackSession>, cb: (Boolean, String?) -> Unit) {
|
fun sendSyncLocalSessions(playbackSessions:List<PlaybackSession>, cb: (Boolean, String?) -> Unit) {
|
||||||
val payload = JSObject(jacksonMapper.writeValueAsString(LocalSessionsSyncRequestPayload(playbackSessions)))
|
val payload = JSObject(jacksonMapper.writeValueAsString(LocalSessionsSyncRequestPayload(playbackSessions)))
|
||||||
|
Log.d(tag, "Sending ${playbackSessions.size} saved local playback sessions to server")
|
||||||
postRequest("/api/session/local-all", payload, null) {
|
postRequest("/api/session/local-all", payload, null) {
|
||||||
if (!it.getString("error").isNullOrEmpty()) {
|
if (!it.getString("error").isNullOrEmpty()) {
|
||||||
|
Log.e(tag, "Failed to sync local sessions")
|
||||||
cb(false, it.getString("error"))
|
cb(false, it.getString("error"))
|
||||||
} else {
|
} else {
|
||||||
val response = jacksonMapper.readValue<LocalSessionsSyncResponsePayload>(it.toString())
|
val response = jacksonMapper.readValue<LocalSessionsSyncResponsePayload>(it.toString())
|
||||||
response.results.forEach { localSessionSyncResult ->
|
response.results.forEach { localSessionSyncResult ->
|
||||||
|
Log.d(tag, "Synced session result ${localSessionSyncResult.id}|${localSessionSyncResult.progressSynced}|${localSessionSyncResult.success}")
|
||||||
playbackSessions.find { ps -> ps.id == localSessionSyncResult.id }?.let { session ->
|
playbackSessions.find { ps -> ps.id == localSessionSyncResult.id }?.let { session ->
|
||||||
if (localSessionSyncResult.progressSynced == true) {
|
if (localSessionSyncResult.progressSynced == true) {
|
||||||
val syncResult = SyncResult(true, true, "Progress synced on server")
|
val syncResult = SyncResult(true, true, "Progress synced on server")
|
||||||
@@ -338,7 +340,6 @@ class ApiHandler(var ctx:Context) {
|
|||||||
} else if (!localSessionSyncResult.success) {
|
} else if (!localSessionSyncResult.success) {
|
||||||
Log.e(tag, "Failed to sync session ${session.displayTitle} with server. Error: ${localSessionSyncResult.error}")
|
Log.e(tag, "Failed to sync session ${session.displayTitle} with server. Error: ${localSessionSyncResult.error}")
|
||||||
}
|
}
|
||||||
DeviceManager.dbManager.removePlaybackSession(session.id)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
cb(true, null)
|
cb(true, null)
|
||||||
@@ -364,6 +365,7 @@ class ApiHandler(var ctx:Context) {
|
|||||||
Log.d(tag, "Server progress for media item id=\"${mediaProgress.mediaItemId}\" is more recent then local. Updating local current time ${localMediaProgress.currentTime} to ${mediaProgress.currentTime}")
|
Log.d(tag, "Server progress for media item id=\"${mediaProgress.mediaItemId}\" is more recent then local. Updating local current time ${localMediaProgress.currentTime} to ${mediaProgress.currentTime}")
|
||||||
localMediaProgress.updateFromServerMediaProgress(mediaProgress)
|
localMediaProgress.updateFromServerMediaProgress(mediaProgress)
|
||||||
MediaEventManager.syncEvent(mediaProgress, "Sync on server connection")
|
MediaEventManager.syncEvent(mediaProgress, "Sync on server connection")
|
||||||
|
DeviceManager.dbManager.saveLocalMediaProgress(localMediaProgress)
|
||||||
} else if (localMediaProgress.lastUpdate > mediaProgress.lastUpdate && localMediaProgress.ebookLocation != null && localMediaProgress.ebookLocation != mediaProgress.ebookLocation) {
|
} else if (localMediaProgress.lastUpdate > mediaProgress.lastUpdate && localMediaProgress.ebookLocation != null && localMediaProgress.ebookLocation != mediaProgress.ebookLocation) {
|
||||||
// Patch ebook progress to server
|
// Patch ebook progress to server
|
||||||
val endpoint = "/api/me/progress/${localMediaProgress.libraryItemId}"
|
val endpoint = "/api/me/progress/${localMediaProgress.libraryItemId}"
|
||||||
|
|||||||
@@ -8,8 +8,8 @@ buildscript {
|
|||||||
mavenCentral()
|
mavenCentral()
|
||||||
}
|
}
|
||||||
dependencies {
|
dependencies {
|
||||||
classpath 'com.google.gms:google-services:4.3.13'
|
classpath 'com.google.gms:google-services:4.4.0'
|
||||||
classpath 'com.android.tools.build:gradle:7.4.0'
|
classpath 'com.android.tools.build:gradle:8.1.1'
|
||||||
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
|
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
|
||||||
|
|
||||||
// NOTE: Do not place your application dependencies here; they belong
|
// NOTE: Do not place your application dependencies here; they belong
|
||||||
|
|||||||
@@ -28,7 +28,7 @@ org.gradle.jvmargs=-Dfile.encoding=UTF-8 \
|
|||||||
# Android operating system, and which are packaged with your app's APK
|
# Android operating system, and which are packaged with your app's APK
|
||||||
# https://developer.android.com/topic/libraries/support-library/androidx-rn
|
# https://developer.android.com/topic/libraries/support-library/androidx-rn
|
||||||
android.useAndroidX=true
|
android.useAndroidX=true
|
||||||
# Automatically convert third-party libraries to use AndroidX
|
|
||||||
android.enableJetifier=true
|
|
||||||
|
|
||||||
kapt.use.worker.api=false
|
kapt.use.worker.api=false
|
||||||
|
|
||||||
|
android.defaults.buildfeatures.buildconfig=true
|
||||||
|
|||||||
BIN
Binary file not shown.
+3
-3
@@ -1,6 +1,6 @@
|
|||||||
#Sun Apr 17 13:28:55 CDT 2022
|
|
||||||
distributionBase=GRADLE_USER_HOME
|
distributionBase=GRADLE_USER_HOME
|
||||||
distributionUrl=https\://services.gradle.org/distributions/gradle-7.5-all.zip
|
|
||||||
distributionPath=wrapper/dists
|
distributionPath=wrapper/dists
|
||||||
zipStorePath=wrapper/dists
|
distributionUrl=https\://services.gradle.org/distributions/gradle-8.0.2-all.zip
|
||||||
|
networkTimeout=10000
|
||||||
zipStoreBase=GRADLE_USER_HOME
|
zipStoreBase=GRADLE_USER_HOME
|
||||||
|
zipStorePath=wrapper/dists
|
||||||
|
|||||||
Vendored
+172
-113
@@ -1,7 +1,7 @@
|
|||||||
#!/usr/bin/env sh
|
#!/bin/sh
|
||||||
|
|
||||||
#
|
#
|
||||||
# Copyright 2015 the original author or authors.
|
# Copyright © 2015-2021 the original authors.
|
||||||
#
|
#
|
||||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
# you may not use this file except in compliance with the License.
|
# you may not use this file except in compliance with the License.
|
||||||
@@ -17,67 +17,101 @@
|
|||||||
#
|
#
|
||||||
|
|
||||||
##############################################################################
|
##############################################################################
|
||||||
##
|
#
|
||||||
## Gradle start up script for UN*X
|
# Gradle start up script for POSIX generated by Gradle.
|
||||||
##
|
#
|
||||||
|
# Important for running:
|
||||||
|
#
|
||||||
|
# (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is
|
||||||
|
# noncompliant, but you have some other compliant shell such as ksh or
|
||||||
|
# bash, then to run this script, type that shell name before the whole
|
||||||
|
# command line, like:
|
||||||
|
#
|
||||||
|
# ksh Gradle
|
||||||
|
#
|
||||||
|
# Busybox and similar reduced shells will NOT work, because this script
|
||||||
|
# requires all of these POSIX shell features:
|
||||||
|
# * functions;
|
||||||
|
# * expansions «$var», «${var}», «${var:-default}», «${var+SET}»,
|
||||||
|
# «${var#prefix}», «${var%suffix}», and «$( cmd )»;
|
||||||
|
# * compound commands having a testable exit status, especially «case»;
|
||||||
|
# * various built-in commands including «command», «set», and «ulimit».
|
||||||
|
#
|
||||||
|
# Important for patching:
|
||||||
|
#
|
||||||
|
# (2) This script targets any POSIX shell, so it avoids extensions provided
|
||||||
|
# by Bash, Ksh, etc; in particular arrays are avoided.
|
||||||
|
#
|
||||||
|
# The "traditional" practice of packing multiple parameters into a
|
||||||
|
# space-separated string is a well documented source of bugs and security
|
||||||
|
# problems, so this is (mostly) avoided, by progressively accumulating
|
||||||
|
# options in "$@", and eventually passing that to Java.
|
||||||
|
#
|
||||||
|
# Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS,
|
||||||
|
# and GRADLE_OPTS) rely on word-splitting, this is performed explicitly;
|
||||||
|
# see the in-line comments for details.
|
||||||
|
#
|
||||||
|
# There are tweaks for specific operating systems such as AIX, CygWin,
|
||||||
|
# Darwin, MinGW, and NonStop.
|
||||||
|
#
|
||||||
|
# (3) This script is generated from the Groovy template
|
||||||
|
# https://github.com/gradle/gradle/blob/HEAD/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt
|
||||||
|
# within the Gradle project.
|
||||||
|
#
|
||||||
|
# You can find Gradle at https://github.com/gradle/gradle/.
|
||||||
|
#
|
||||||
##############################################################################
|
##############################################################################
|
||||||
|
|
||||||
# Attempt to set APP_HOME
|
# Attempt to set APP_HOME
|
||||||
# Resolve links: $0 may be a link
|
|
||||||
PRG="$0"
|
|
||||||
# Need this for relative symlinks.
|
|
||||||
while [ -h "$PRG" ] ; do
|
|
||||||
ls=`ls -ld "$PRG"`
|
|
||||||
link=`expr "$ls" : '.*-> \(.*\)$'`
|
|
||||||
if expr "$link" : '/.*' > /dev/null; then
|
|
||||||
PRG="$link"
|
|
||||||
else
|
|
||||||
PRG=`dirname "$PRG"`"/$link"
|
|
||||||
fi
|
|
||||||
done
|
|
||||||
SAVED="`pwd`"
|
|
||||||
cd "`dirname \"$PRG\"`/" >/dev/null
|
|
||||||
APP_HOME="`pwd -P`"
|
|
||||||
cd "$SAVED" >/dev/null
|
|
||||||
|
|
||||||
APP_NAME="Gradle"
|
# Resolve links: $0 may be a link
|
||||||
APP_BASE_NAME=`basename "$0"`
|
app_path=$0
|
||||||
|
|
||||||
|
# Need this for daisy-chained symlinks.
|
||||||
|
while
|
||||||
|
APP_HOME=${app_path%"${app_path##*/}"} # leaves a trailing /; empty if no leading path
|
||||||
|
[ -h "$app_path" ]
|
||||||
|
do
|
||||||
|
ls=$( ls -ld "$app_path" )
|
||||||
|
link=${ls#*' -> '}
|
||||||
|
case $link in #(
|
||||||
|
/*) app_path=$link ;; #(
|
||||||
|
*) app_path=$APP_HOME$link ;;
|
||||||
|
esac
|
||||||
|
done
|
||||||
|
|
||||||
|
# This is normally unused
|
||||||
|
# shellcheck disable=SC2034
|
||||||
|
APP_BASE_NAME=${0##*/}
|
||||||
|
APP_HOME=$( cd "${APP_HOME:-./}" && pwd -P ) || exit
|
||||||
|
|
||||||
# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
|
# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
|
||||||
DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
|
DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
|
||||||
|
|
||||||
# Use the maximum available, or set MAX_FD != -1 to use that value.
|
# Use the maximum available, or set MAX_FD != -1 to use that value.
|
||||||
MAX_FD="maximum"
|
MAX_FD=maximum
|
||||||
|
|
||||||
warn () {
|
warn () {
|
||||||
echo "$*"
|
echo "$*"
|
||||||
}
|
} >&2
|
||||||
|
|
||||||
die () {
|
die () {
|
||||||
echo
|
echo
|
||||||
echo "$*"
|
echo "$*"
|
||||||
echo
|
echo
|
||||||
exit 1
|
exit 1
|
||||||
}
|
} >&2
|
||||||
|
|
||||||
# OS specific support (must be 'true' or 'false').
|
# OS specific support (must be 'true' or 'false').
|
||||||
cygwin=false
|
cygwin=false
|
||||||
msys=false
|
msys=false
|
||||||
darwin=false
|
darwin=false
|
||||||
nonstop=false
|
nonstop=false
|
||||||
case "`uname`" in
|
case "$( uname )" in #(
|
||||||
CYGWIN* )
|
CYGWIN* ) cygwin=true ;; #(
|
||||||
cygwin=true
|
Darwin* ) darwin=true ;; #(
|
||||||
;;
|
MSYS* | MINGW* ) msys=true ;; #(
|
||||||
Darwin* )
|
NONSTOP* ) nonstop=true ;;
|
||||||
darwin=true
|
|
||||||
;;
|
|
||||||
MINGW* )
|
|
||||||
msys=true
|
|
||||||
;;
|
|
||||||
NONSTOP* )
|
|
||||||
nonstop=true
|
|
||||||
;;
|
|
||||||
esac
|
esac
|
||||||
|
|
||||||
CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
|
CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
|
||||||
@@ -87,9 +121,9 @@ CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
|
|||||||
if [ -n "$JAVA_HOME" ] ; then
|
if [ -n "$JAVA_HOME" ] ; then
|
||||||
if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
|
if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
|
||||||
# IBM's JDK on AIX uses strange locations for the executables
|
# IBM's JDK on AIX uses strange locations for the executables
|
||||||
JAVACMD="$JAVA_HOME/jre/sh/java"
|
JAVACMD=$JAVA_HOME/jre/sh/java
|
||||||
else
|
else
|
||||||
JAVACMD="$JAVA_HOME/bin/java"
|
JAVACMD=$JAVA_HOME/bin/java
|
||||||
fi
|
fi
|
||||||
if [ ! -x "$JAVACMD" ] ; then
|
if [ ! -x "$JAVACMD" ] ; then
|
||||||
die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
|
die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
|
||||||
@@ -98,7 +132,7 @@ Please set the JAVA_HOME variable in your environment to match the
|
|||||||
location of your Java installation."
|
location of your Java installation."
|
||||||
fi
|
fi
|
||||||
else
|
else
|
||||||
JAVACMD="java"
|
JAVACMD=java
|
||||||
which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
|
which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
|
||||||
|
|
||||||
Please set the JAVA_HOME variable in your environment to match the
|
Please set the JAVA_HOME variable in your environment to match the
|
||||||
@@ -106,80 +140,105 @@ location of your Java installation."
|
|||||||
fi
|
fi
|
||||||
|
|
||||||
# Increase the maximum file descriptors if we can.
|
# Increase the maximum file descriptors if we can.
|
||||||
if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then
|
if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then
|
||||||
MAX_FD_LIMIT=`ulimit -H -n`
|
case $MAX_FD in #(
|
||||||
if [ $? -eq 0 ] ; then
|
max*)
|
||||||
if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
|
# In POSIX sh, ulimit -H is undefined. That's why the result is checked to see if it worked.
|
||||||
MAX_FD="$MAX_FD_LIMIT"
|
# shellcheck disable=SC3045
|
||||||
fi
|
MAX_FD=$( ulimit -H -n ) ||
|
||||||
ulimit -n $MAX_FD
|
warn "Could not query maximum file descriptor limit"
|
||||||
if [ $? -ne 0 ] ; then
|
esac
|
||||||
warn "Could not set maximum file descriptor limit: $MAX_FD"
|
case $MAX_FD in #(
|
||||||
fi
|
'' | soft) :;; #(
|
||||||
else
|
*)
|
||||||
warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
|
# In POSIX sh, ulimit -n is undefined. That's why the result is checked to see if it worked.
|
||||||
fi
|
# shellcheck disable=SC3045
|
||||||
fi
|
ulimit -n "$MAX_FD" ||
|
||||||
|
warn "Could not set maximum file descriptor limit to $MAX_FD"
|
||||||
# For Darwin, add options to specify how the application appears in the dock
|
|
||||||
if $darwin; then
|
|
||||||
GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
|
|
||||||
fi
|
|
||||||
|
|
||||||
# For Cygwin or MSYS, switch paths to Windows format before running java
|
|
||||||
if [ "$cygwin" = "true" -o "$msys" = "true" ] ; then
|
|
||||||
APP_HOME=`cygpath --path --mixed "$APP_HOME"`
|
|
||||||
CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
|
|
||||||
|
|
||||||
JAVACMD=`cygpath --unix "$JAVACMD"`
|
|
||||||
|
|
||||||
# We build the pattern for arguments to be converted via cygpath
|
|
||||||
ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
|
|
||||||
SEP=""
|
|
||||||
for dir in $ROOTDIRSRAW ; do
|
|
||||||
ROOTDIRS="$ROOTDIRS$SEP$dir"
|
|
||||||
SEP="|"
|
|
||||||
done
|
|
||||||
OURCYGPATTERN="(^($ROOTDIRS))"
|
|
||||||
# Add a user-defined pattern to the cygpath arguments
|
|
||||||
if [ "$GRADLE_CYGPATTERN" != "" ] ; then
|
|
||||||
OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
|
|
||||||
fi
|
|
||||||
# Now convert the arguments - kludge to limit ourselves to /bin/sh
|
|
||||||
i=0
|
|
||||||
for arg in "$@" ; do
|
|
||||||
CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
|
|
||||||
CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option
|
|
||||||
|
|
||||||
if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition
|
|
||||||
eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
|
|
||||||
else
|
|
||||||
eval `echo args$i`="\"$arg\""
|
|
||||||
fi
|
|
||||||
i=`expr $i + 1`
|
|
||||||
done
|
|
||||||
case $i in
|
|
||||||
0) set -- ;;
|
|
||||||
1) set -- "$args0" ;;
|
|
||||||
2) set -- "$args0" "$args1" ;;
|
|
||||||
3) set -- "$args0" "$args1" "$args2" ;;
|
|
||||||
4) set -- "$args0" "$args1" "$args2" "$args3" ;;
|
|
||||||
5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
|
|
||||||
6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
|
|
||||||
7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
|
|
||||||
8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
|
|
||||||
9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
|
|
||||||
esac
|
esac
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# Escape application args
|
# Collect all arguments for the java command, stacking in reverse order:
|
||||||
save () {
|
# * args from the command line
|
||||||
for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done
|
# * the main class name
|
||||||
echo " "
|
# * -classpath
|
||||||
}
|
# * -D...appname settings
|
||||||
APP_ARGS=`save "$@"`
|
# * --module-path (only if needed)
|
||||||
|
# * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables.
|
||||||
|
|
||||||
# Collect all arguments for the java command, following the shell quoting and substitution rules
|
# For Cygwin or MSYS, switch paths to Windows format before running java
|
||||||
eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS"
|
if "$cygwin" || "$msys" ; then
|
||||||
|
APP_HOME=$( cygpath --path --mixed "$APP_HOME" )
|
||||||
|
CLASSPATH=$( cygpath --path --mixed "$CLASSPATH" )
|
||||||
|
|
||||||
|
JAVACMD=$( cygpath --unix "$JAVACMD" )
|
||||||
|
|
||||||
|
# Now convert the arguments - kludge to limit ourselves to /bin/sh
|
||||||
|
for arg do
|
||||||
|
if
|
||||||
|
case $arg in #(
|
||||||
|
-*) false ;; # don't mess with options #(
|
||||||
|
/?*) t=${arg#/} t=/${t%%/*} # looks like a POSIX filepath
|
||||||
|
[ -e "$t" ] ;; #(
|
||||||
|
*) false ;;
|
||||||
|
esac
|
||||||
|
then
|
||||||
|
arg=$( cygpath --path --ignore --mixed "$arg" )
|
||||||
|
fi
|
||||||
|
# Roll the args list around exactly as many times as the number of
|
||||||
|
# args, so each arg winds up back in the position where it started, but
|
||||||
|
# possibly modified.
|
||||||
|
#
|
||||||
|
# NB: a `for` loop captures its iteration list before it begins, so
|
||||||
|
# changing the positional parameters here affects neither the number of
|
||||||
|
# iterations, nor the values presented in `arg`.
|
||||||
|
shift # remove old arg
|
||||||
|
set -- "$@" "$arg" # push replacement arg
|
||||||
|
done
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Collect all arguments for the java command;
|
||||||
|
# * $DEFAULT_JVM_OPTS, $JAVA_OPTS, and $GRADLE_OPTS can contain fragments of
|
||||||
|
# shell script including quotes and variable substitutions, so put them in
|
||||||
|
# double quotes to make sure that they get re-expanded; and
|
||||||
|
# * put everything else in single quotes, so that it's not re-expanded.
|
||||||
|
|
||||||
|
set -- \
|
||||||
|
"-Dorg.gradle.appname=$APP_BASE_NAME" \
|
||||||
|
-classpath "$CLASSPATH" \
|
||||||
|
org.gradle.wrapper.GradleWrapperMain \
|
||||||
|
"$@"
|
||||||
|
|
||||||
|
# Stop when "xargs" is not available.
|
||||||
|
if ! command -v xargs >/dev/null 2>&1
|
||||||
|
then
|
||||||
|
die "xargs is not available"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Use "xargs" to parse quoted args.
|
||||||
|
#
|
||||||
|
# With -n1 it outputs one arg per line, with the quotes and backslashes removed.
|
||||||
|
#
|
||||||
|
# In Bash we could simply go:
|
||||||
|
#
|
||||||
|
# readarray ARGS < <( xargs -n1 <<<"$var" ) &&
|
||||||
|
# set -- "${ARGS[@]}" "$@"
|
||||||
|
#
|
||||||
|
# but POSIX shell has neither arrays nor command substitution, so instead we
|
||||||
|
# post-process each arg (as a line of input to sed) to backslash-escape any
|
||||||
|
# character that might be a shell metacharacter, then use eval to reverse
|
||||||
|
# that process (while maintaining the separation between arguments), and wrap
|
||||||
|
# the whole thing up as a single "set" statement.
|
||||||
|
#
|
||||||
|
# This will of course break if any of these variables contains a newline or
|
||||||
|
# an unmatched quote.
|
||||||
|
#
|
||||||
|
|
||||||
|
eval "set -- $(
|
||||||
|
printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" |
|
||||||
|
xargs -n1 |
|
||||||
|
sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' |
|
||||||
|
tr '\n' ' '
|
||||||
|
)" '"$@"'
|
||||||
|
|
||||||
exec "$JAVACMD" "$@"
|
exec "$JAVACMD" "$@"
|
||||||
|
|||||||
Vendored
+9
-6
@@ -14,7 +14,7 @@
|
|||||||
@rem limitations under the License.
|
@rem limitations under the License.
|
||||||
@rem
|
@rem
|
||||||
|
|
||||||
@if "%DEBUG%" == "" @echo off
|
@if "%DEBUG%"=="" @echo off
|
||||||
@rem ##########################################################################
|
@rem ##########################################################################
|
||||||
@rem
|
@rem
|
||||||
@rem Gradle startup script for Windows
|
@rem Gradle startup script for Windows
|
||||||
@@ -25,7 +25,8 @@
|
|||||||
if "%OS%"=="Windows_NT" setlocal
|
if "%OS%"=="Windows_NT" setlocal
|
||||||
|
|
||||||
set DIRNAME=%~dp0
|
set DIRNAME=%~dp0
|
||||||
if "%DIRNAME%" == "" set DIRNAME=.
|
if "%DIRNAME%"=="" set DIRNAME=.
|
||||||
|
@rem This is normally unused
|
||||||
set APP_BASE_NAME=%~n0
|
set APP_BASE_NAME=%~n0
|
||||||
set APP_HOME=%DIRNAME%
|
set APP_HOME=%DIRNAME%
|
||||||
|
|
||||||
@@ -40,7 +41,7 @@ if defined JAVA_HOME goto findJavaFromJavaHome
|
|||||||
|
|
||||||
set JAVA_EXE=java.exe
|
set JAVA_EXE=java.exe
|
||||||
%JAVA_EXE% -version >NUL 2>&1
|
%JAVA_EXE% -version >NUL 2>&1
|
||||||
if "%ERRORLEVEL%" == "0" goto execute
|
if %ERRORLEVEL% equ 0 goto execute
|
||||||
|
|
||||||
echo.
|
echo.
|
||||||
echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
|
echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
|
||||||
@@ -75,13 +76,15 @@ set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
|
|||||||
|
|
||||||
:end
|
:end
|
||||||
@rem End local scope for the variables with windows NT shell
|
@rem End local scope for the variables with windows NT shell
|
||||||
if "%ERRORLEVEL%"=="0" goto mainEnd
|
if %ERRORLEVEL% equ 0 goto mainEnd
|
||||||
|
|
||||||
:fail
|
:fail
|
||||||
rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
|
rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
|
||||||
rem the _cmd.exe /c_ return code!
|
rem the _cmd.exe /c_ return code!
|
||||||
if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
|
set EXIT_CODE=%ERRORLEVEL%
|
||||||
exit /b 1
|
if %EXIT_CODE% equ 0 set EXIT_CODE=1
|
||||||
|
if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE%
|
||||||
|
exit /b %EXIT_CODE%
|
||||||
|
|
||||||
:mainEnd
|
:mainEnd
|
||||||
if "%OS%"=="Windows_NT" endlocal
|
if "%OS%"=="Windows_NT" endlocal
|
||||||
|
|||||||
+12
-12
@@ -1,19 +1,19 @@
|
|||||||
ext {
|
ext {
|
||||||
minSdkVersion = 24
|
minSdkVersion = 24
|
||||||
compileSdkVersion = 33
|
compileSdkVersion = 34
|
||||||
targetSdkVersion = 33
|
targetSdkVersion = 33
|
||||||
androidxActivityVersion = '1.4.0'
|
androidxActivityVersion = '1.7.0'
|
||||||
androidxAppCompatVersion = '1.4.2'
|
androidxAppCompatVersion = '1.6.1'
|
||||||
androidxCoordinatorLayoutVersion = '1.2.0'
|
androidxCoordinatorLayoutVersion = '1.2.0'
|
||||||
androidxCoreVersion = '1.8.0'
|
androidxCoreVersion = '1.10.0'
|
||||||
androidPlayCore = '1.9.0'
|
androidPlayCore = '1.9.0'
|
||||||
androidxFragmentVersion = '1.4.1'
|
androidxFragmentVersion = '1.5.6'
|
||||||
junitVersion = '4.13.2'
|
junitVersion = '4.13.2'
|
||||||
androidxJunitVersion = '1.1.3'
|
androidxJunitVersion = '1.1.5'
|
||||||
androidxEspressoCoreVersion = '3.4.0'
|
androidxEspressoCoreVersion = '3.5.1'
|
||||||
cordovaAndroidVersion = '10.1.1'
|
cordovaAndroidVersion = '10.1.1'
|
||||||
androidx_car_version = '1.0.0-alpha7'
|
androidx_car_version = '1.0.0-alpha7'
|
||||||
androidx_core_ktx_version = '1.7.0'
|
androidx_core_ktx_version = '1.12.0'
|
||||||
androidx_media_version = '1.6.0'
|
androidx_media_version = '1.6.0'
|
||||||
androidx_preference_version = '1.1.1'
|
androidx_preference_version = '1.1.1'
|
||||||
androidx_test_runner_version = '1.3.0'
|
androidx_test_runner_version = '1.3.0'
|
||||||
@@ -27,13 +27,13 @@ ext {
|
|||||||
gradle_version = '3.1.4'
|
gradle_version = '3.1.4'
|
||||||
gson_version = '2.8.5'
|
gson_version = '2.8.5'
|
||||||
junit_version = '4.13'
|
junit_version = '4.13'
|
||||||
kotlin_version = '1.7.20'
|
kotlin_version = '1.8.10'
|
||||||
kotlin_coroutines_version = '1.1.0'
|
kotlin_coroutines_version = '1.6.4'
|
||||||
multidex_version = '1.0.3'
|
multidex_version = '1.0.3'
|
||||||
play_services_auth_version = '18.1.0'
|
play_services_auth_version = '18.1.0'
|
||||||
recycler_view_version = '1.1.0'
|
recycler_view_version = '1.1.0'
|
||||||
robolectric_version = '4.2'
|
robolectric_version = '4.2'
|
||||||
test_runner_version = '1.1.0'
|
test_runner_version = '1.1.0'
|
||||||
coreSplashScreenVersion = '1.0.0-rc01'
|
coreSplashScreenVersion = '1.0.1'
|
||||||
androidxWebkitVersion = '1.4.0'
|
androidxWebkitVersion = '1.6.1'
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,5 +2,10 @@
|
|||||||
"appId": "com.audiobookshelf.app",
|
"appId": "com.audiobookshelf.app",
|
||||||
"appName": "audiobookshelf-app",
|
"appName": "audiobookshelf-app",
|
||||||
"webDir": "dist",
|
"webDir": "dist",
|
||||||
"bundledWebRuntime": false
|
"bundledWebRuntime": false,
|
||||||
|
"plugins": {
|
||||||
|
"CapacitorHttp": {
|
||||||
|
"enabled": false
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -48,7 +48,7 @@
|
|||||||
<div id="playerContent" class="playerContainer w-full z-20 absolute bottom-0 left-0 right-0 p-2 pointer-events-auto transition-all" :style="{ backgroundColor: showFullscreen ? '' : coverRgb }" @click="clickContainer">
|
<div id="playerContent" class="playerContainer w-full z-20 absolute bottom-0 left-0 right-0 p-2 pointer-events-auto transition-all" :style="{ backgroundColor: showFullscreen ? '' : coverRgb }" @click="clickContainer">
|
||||||
<div v-if="showFullscreen" class="absolute bottom-4 left-0 right-0 w-full pb-4 pt-2 mx-auto px-6" style="max-width: 414px">
|
<div v-if="showFullscreen" class="absolute bottom-4 left-0 right-0 w-full pb-4 pt-2 mx-auto px-6" style="max-width: 414px">
|
||||||
<div class="flex items-center justify-between pointer-events-auto">
|
<div class="flex items-center justify-between pointer-events-auto">
|
||||||
<span v-if="!isPodcast && isServerItem && networkConnected" class="material-icons text-3xl text-white text-opacity-75 cursor-pointer" @click="$emit('showBookmarks')">{{ bookmarks.length ? 'bookmark' : 'bookmark_border' }}</span>
|
<span v-if="!isPodcast && serverLibraryItemId && networkConnected" class="material-icons text-3xl text-white text-opacity-75 cursor-pointer" @click="$emit('showBookmarks')">{{ bookmarks.length ? 'bookmark' : 'bookmark_border' }}</span>
|
||||||
<!-- hidden for podcasts but still using this as a placeholder -->
|
<!-- hidden for podcasts but still using this as a placeholder -->
|
||||||
<span v-else class="material-icons text-3xl text-white text-opacity-0">bookmark</span>
|
<span v-else class="material-icons text-3xl text-white text-opacity-0">bookmark</span>
|
||||||
|
|
||||||
@@ -115,7 +115,7 @@ export default {
|
|||||||
},
|
},
|
||||||
sleepTimerRunning: Boolean,
|
sleepTimerRunning: Boolean,
|
||||||
sleepTimeRemaining: Number,
|
sleepTimeRemaining: Number,
|
||||||
isServerItem: Boolean
|
serverLibraryItemId: String
|
||||||
},
|
},
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
@@ -391,7 +391,7 @@ export default {
|
|||||||
},
|
},
|
||||||
clickTitleAndAuthor() {
|
clickTitleAndAuthor() {
|
||||||
if (!this.showFullscreen) return
|
if (!this.showFullscreen) return
|
||||||
const llid = this.libraryItem ? this.libraryItem.id : this.localLibraryItem ? this.localLibraryItem.id : null
|
const llid = this.serverLibraryItemId || this.libraryItem?.id || this.localLibraryItem?.id
|
||||||
if (llid) {
|
if (llid) {
|
||||||
this.$router.push(`/item/${llid}`)
|
this.$router.push(`/item/${llid}`)
|
||||||
this.showFullscreen = false
|
this.showFullscreen = false
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
<template>
|
<template>
|
||||||
<div>
|
<div>
|
||||||
<app-audio-player ref="audioPlayer" :bookmarks="bookmarks" :sleep-timer-running="isSleepTimerRunning" :sleep-time-remaining="sleepTimeRemaining" :is-server-item="!!serverLibraryItemId" @selectPlaybackSpeed="showPlaybackSpeedModal = true" @updateTime="(t) => (currentTime = t)" @showSleepTimer="showSleepTimer" @showBookmarks="showBookmarks" />
|
<app-audio-player ref="audioPlayer" :bookmarks="bookmarks" :sleep-timer-running="isSleepTimerRunning" :sleep-time-remaining="sleepTimeRemaining" :serverLibraryItemId="serverLibraryItemId" @selectPlaybackSpeed="showPlaybackSpeedModal = true" @updateTime="(t) => (currentTime = t)" @showSleepTimer="showSleepTimer" @showBookmarks="showBookmarks" />
|
||||||
|
|
||||||
<modals-playback-speed-modal v-model="showPlaybackSpeedModal" :playback-rate.sync="playbackSpeed" @update:playbackRate="updatePlaybackSpeed" @change="changePlaybackSpeed" />
|
<modals-playback-speed-modal v-model="showPlaybackSpeedModal" :playback-rate.sync="playbackSpeed" @update:playbackRate="updatePlaybackSpeed" @change="changePlaybackSpeed" />
|
||||||
<modals-sleep-timer-modal v-model="showSleepTimerModal" :current-time="sleepTimeRemaining" :sleep-timer-running="isSleepTimerRunning" :current-end-of-chapter-time="currentEndOfChapterTime" :is-auto="isAutoSleepTimer" @change="selectSleepTimeout" @cancel="cancelSleepTimer" @increase="increaseSleepTimer" @decrease="decreaseSleepTimer" />
|
<modals-sleep-timer-modal v-model="showSleepTimerModal" :current-time="sleepTimeRemaining" :sleep-timer-running="isSleepTimerRunning" :current-end-of-chapter-time="currentEndOfChapterTime" :is-auto="isAutoSleepTimer" @change="selectSleepTimeout" @cancel="cancelSleepTimer" @increase="increaseSleepTimer" @decrease="decreaseSleepTimer" />
|
||||||
@@ -256,7 +256,7 @@ export default {
|
|||||||
})
|
})
|
||||||
},
|
},
|
||||||
pauseItem() {
|
pauseItem() {
|
||||||
if (this.$refs.audioPlayer && !this.$refs.audioPlayer.isPaused) {
|
if (this.$refs.audioPlayer && this.$refs.audioPlayer.isPlaying) {
|
||||||
this.$refs.audioPlayer.pause()
|
this.$refs.audioPlayer.pause()
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@@ -285,6 +285,53 @@ export default {
|
|||||||
},
|
},
|
||||||
playbackTimeUpdate(currentTime) {
|
playbackTimeUpdate(currentTime) {
|
||||||
this.$refs.audioPlayer?.seek(currentTime)
|
this.$refs.audioPlayer?.seek(currentTime)
|
||||||
|
},
|
||||||
|
/**
|
||||||
|
* When device gains focus then refresh the timestamps in the audio player
|
||||||
|
*/
|
||||||
|
deviceFocused(hasFocus) {
|
||||||
|
if (!this.$store.state.currentPlaybackSession) return
|
||||||
|
|
||||||
|
if (hasFocus) {
|
||||||
|
if (!this.$refs.audioPlayer?.isPlaying) {
|
||||||
|
const playbackSession = this.$store.state.currentPlaybackSession
|
||||||
|
if (this.$refs.audioPlayer.isLocalPlayMethod) {
|
||||||
|
const localLibraryItemId = playbackSession.localLibraryItem?.id
|
||||||
|
const localEpisodeId = playbackSession.localEpisodeId
|
||||||
|
if (!localLibraryItemId) {
|
||||||
|
console.error('[AudioPlayerContainer] device visibility: no local library item for session', JSON.stringify(playbackSession))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
const localMediaProgress = this.$store.state.globals.localMediaProgress.find((mp) => {
|
||||||
|
if (localEpisodeId) return mp.localEpisodeId === localEpisodeId
|
||||||
|
return mp.localLibraryItemId === localLibraryItemId
|
||||||
|
})
|
||||||
|
if (localMediaProgress) {
|
||||||
|
console.log('[AudioPlayerContainer] device visibility: found local media progress', localMediaProgress.currentTime, 'last time in player is', this.currentTime)
|
||||||
|
this.$refs.audioPlayer.currentTime = localMediaProgress.currentTime
|
||||||
|
this.$refs.audioPlayer.timeupdate()
|
||||||
|
} else {
|
||||||
|
console.error('[AudioPlayerContainer] device visibility: Local media progress not found')
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
const libraryItemId = playbackSession.libraryItemId
|
||||||
|
const episodeId = playbackSession.episodeId
|
||||||
|
const url = episodeId ? `/api/me/progress/${libraryItemId}/${episodeId}` : `/api/me/progress/${libraryItemId}`
|
||||||
|
this.$nativeHttp
|
||||||
|
.get(url)
|
||||||
|
.then((data) => {
|
||||||
|
if (!this.$refs.audioPlayer?.isPlaying && data.libraryItemId === libraryItemId) {
|
||||||
|
console.log('[AudioPlayerContainer] device visibility: got server media progress', data.currentTime, 'last time in player is', this.currentTime)
|
||||||
|
this.$refs.audioPlayer.currentTime = data.currentTime
|
||||||
|
this.$refs.audioPlayer.timeupdate()
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.catch((error) => {
|
||||||
|
console.error('[AudioPlayerContainer] device visibility: Failed to get progress', error)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
mounted() {
|
mounted() {
|
||||||
@@ -303,6 +350,7 @@ export default {
|
|||||||
this.$eventBus.$on('cast-local-item', this.castLocalItem)
|
this.$eventBus.$on('cast-local-item', this.castLocalItem)
|
||||||
this.$eventBus.$on('user-settings', this.settingsUpdated)
|
this.$eventBus.$on('user-settings', this.settingsUpdated)
|
||||||
this.$eventBus.$on('playback-time-update', this.playbackTimeUpdate)
|
this.$eventBus.$on('playback-time-update', this.playbackTimeUpdate)
|
||||||
|
this.$eventBus.$on('device-focus-update', this.deviceFocused)
|
||||||
},
|
},
|
||||||
beforeDestroy() {
|
beforeDestroy() {
|
||||||
if (this.onLocalMediaProgressUpdateListener) this.onLocalMediaProgressUpdateListener.remove()
|
if (this.onLocalMediaProgressUpdateListener) this.onLocalMediaProgressUpdateListener.remove()
|
||||||
@@ -317,6 +365,7 @@ export default {
|
|||||||
this.$eventBus.$off('cast-local-item', this.castLocalItem)
|
this.$eventBus.$off('cast-local-item', this.castLocalItem)
|
||||||
this.$eventBus.$off('user-settings', this.settingsUpdated)
|
this.$eventBus.$off('user-settings', this.settingsUpdated)
|
||||||
this.$eventBus.$off('playback-time-update', this.playbackTimeUpdate)
|
this.$eventBus.$off('playback-time-update', this.playbackTimeUpdate)
|
||||||
|
this.$eventBus.$off('device-focus-update', this.deviceFocused)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
@@ -11,7 +11,11 @@
|
|||||||
|
|
||||||
<div class="w-full overflow-y-auto">
|
<div class="w-full overflow-y-auto">
|
||||||
<template v-for="item in navItems">
|
<template v-for="item in navItems">
|
||||||
<nuxt-link :to="item.to" :key="item.text" class="w-full hover:bg-bg hover:bg-opacity-60 flex items-center py-3 px-6 text-gray-300" :class="currentRoutePath.startsWith(item.to) ? 'bg-bg bg-opacity-60' : ''">
|
<button v-if="item.action" :key="item.text" class="w-full hover:bg-bg hover:bg-opacity-60 flex items-center py-3 px-6 text-gray-300" @click="clickAction(item.action)">
|
||||||
|
<span class="text-lg" :class="item.iconOutlined ? 'material-icons-outlined' : 'material-icons'">{{ item.icon }}</span>
|
||||||
|
<p class="pl-4">{{ item.text }}</p>
|
||||||
|
</button>
|
||||||
|
<nuxt-link v-else :to="item.to" :key="item.text" class="w-full hover:bg-bg hover:bg-opacity-60 flex items-center py-3 px-6 text-gray-300" :class="currentRoutePath.startsWith(item.to) ? 'bg-bg bg-opacity-60' : ''">
|
||||||
<span class="text-lg" :class="item.iconOutlined ? 'material-icons-outlined' : 'material-icons'">{{ item.icon }}</span>
|
<span class="text-lg" :class="item.iconOutlined ? 'material-icons-outlined' : 'material-icons'">{{ item.icon }}</span>
|
||||||
<p class="pl-4">{{ item.text }}</p>
|
<p class="pl-4">{{ item.text }}</p>
|
||||||
</nuxt-link>
|
</nuxt-link>
|
||||||
@@ -24,9 +28,9 @@
|
|||||||
<div class="flex items-center">
|
<div class="flex items-center">
|
||||||
<p class="text-xs">{{ $config.version }}</p>
|
<p class="text-xs">{{ $config.version }}</p>
|
||||||
<div class="flex-grow" />
|
<div class="flex-grow" />
|
||||||
<div v-if="user" class="flex items-center" @click="logout">
|
<div v-if="user" class="flex items-center" @click="disconnect">
|
||||||
<p class="text-xs pr-2">Logout</p>
|
<p class="text-xs pr-2">Disconnect</p>
|
||||||
<span class="material-icons text-sm">logout</span>
|
<i class="material-icons text-sm -mb-0.5">cloud_off</i>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -126,6 +130,15 @@ export default {
|
|||||||
text: 'Settings',
|
text: 'Settings',
|
||||||
to: '/settings'
|
to: '/settings'
|
||||||
})
|
})
|
||||||
|
|
||||||
|
if (this.serverConnectionConfig) {
|
||||||
|
items.push({
|
||||||
|
icon: 'login',
|
||||||
|
text: 'Switch Server/User',
|
||||||
|
action: 'logout'
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
return items
|
return items
|
||||||
},
|
},
|
||||||
currentRoutePath() {
|
currentRoutePath() {
|
||||||
@@ -133,14 +146,20 @@ export default {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
|
async clickAction(action) {
|
||||||
|
await this.$hapticsImpact()
|
||||||
|
if (action === 'logout') {
|
||||||
|
await this.logout()
|
||||||
|
this.$router.push('/connect')
|
||||||
|
}
|
||||||
|
},
|
||||||
clickBackground() {
|
clickBackground() {
|
||||||
this.show = false
|
this.show = false
|
||||||
},
|
},
|
||||||
async logout() {
|
async logout() {
|
||||||
await this.$hapticsImpact()
|
|
||||||
if (this.user) {
|
if (this.user) {
|
||||||
await this.$axios.$post('/logout').catch((error) => {
|
await this.$nativeHttp.post('/logout').catch((error) => {
|
||||||
console.error(error)
|
console.error('Failed to logout', error)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -148,7 +167,17 @@ export default {
|
|||||||
await this.$db.logout()
|
await this.$db.logout()
|
||||||
this.$localStore.removeLastLibraryId()
|
this.$localStore.removeLastLibraryId()
|
||||||
this.$store.commit('user/logout')
|
this.$store.commit('user/logout')
|
||||||
this.$router.push('/connect')
|
this.$store.commit('libraries/setCurrentLibrary', null)
|
||||||
|
},
|
||||||
|
async disconnect() {
|
||||||
|
await this.$hapticsImpact()
|
||||||
|
await this.logout()
|
||||||
|
|
||||||
|
if (this.$route.name !== 'bookshelf') {
|
||||||
|
this.$router.replace('/bookshelf')
|
||||||
|
} else {
|
||||||
|
location.reload()
|
||||||
|
}
|
||||||
},
|
},
|
||||||
touchstart(e) {
|
touchstart(e) {
|
||||||
this.touchEvent = new TouchEvent(e)
|
this.touchEvent = new TouchEvent(e)
|
||||||
|
|||||||
@@ -164,9 +164,9 @@ export default {
|
|||||||
|
|
||||||
const entityPath = this.entityName === 'books' || this.entityName === 'series-books' ? `items` : this.entityName
|
const entityPath = this.entityName === 'books' || this.entityName === 'series-books' ? `items` : this.entityName
|
||||||
const sfQueryString = this.currentSFQueryString ? this.currentSFQueryString + '&' : ''
|
const sfQueryString = this.currentSFQueryString ? this.currentSFQueryString + '&' : ''
|
||||||
const fullQueryString = `?${sfQueryString}limit=${this.booksPerFetch}&page=${page}&minified=1&include=rssfeed`
|
const fullQueryString = `?${sfQueryString}limit=${this.booksPerFetch}&page=${page}&minified=1&include=rssfeed,numEpisodesIncomplete`
|
||||||
|
|
||||||
const payload = await this.$axios.$get(`/api/libraries/${this.currentLibraryId}/${entityPath}${fullQueryString}`).catch((error) => {
|
const payload = await this.$nativeHttp.get(`/api/libraries/${this.currentLibraryId}/${entityPath}${fullQueryString}`).catch((error) => {
|
||||||
console.error('failed to fetch books', error)
|
console.error('failed to fetch books', error)
|
||||||
return null
|
return null
|
||||||
})
|
})
|
||||||
@@ -332,6 +332,12 @@ export default {
|
|||||||
},
|
},
|
||||||
async init() {
|
async init() {
|
||||||
if (this.isFirstInit) return
|
if (this.isFirstInit) return
|
||||||
|
if (!this.user) {
|
||||||
|
// Offline support not available
|
||||||
|
await this.resetEntities()
|
||||||
|
this.$eventBus.$emit('bookshelf-total-entities', 0)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
this.localLibraryItems = await this.$db.getLocalLibraryItems(this.currentLibraryMediaType)
|
this.localLibraryItems = await this.$db.getLocalLibraryItems(this.currentLibraryMediaType)
|
||||||
console.log('Local library items loaded for lazy bookshelf', this.localLibraryItems.length)
|
console.log('Local library items loaded for lazy bookshelf', this.localLibraryItems.length)
|
||||||
|
|||||||
@@ -58,24 +58,7 @@ export default {
|
|||||||
return this._author.numBooks || 0
|
return this._author.numBooks || 0
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {},
|
||||||
async searchAuthor() {
|
|
||||||
this.searching = true
|
|
||||||
var response = await this.$axios.$post(`/api/authors/${this.authorId}/match`, { q: this.name }).catch((error) => {
|
|
||||||
console.error('Failed', error)
|
|
||||||
return null
|
|
||||||
})
|
|
||||||
if (!response) {
|
|
||||||
this.$toast.error('Author not found')
|
|
||||||
} else if (response.updated) {
|
|
||||||
if (response.author.imagePath) this.$toast.success('Author was updated')
|
|
||||||
else this.$toast.success('Author was updated (no image found)')
|
|
||||||
} else {
|
|
||||||
this.$toast.info('No updates were made for Author')
|
|
||||||
}
|
|
||||||
this.searching = false
|
|
||||||
}
|
|
||||||
},
|
|
||||||
mounted() {}
|
mounted() {}
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
@@ -37,6 +37,12 @@
|
|||||||
<div v-if="!hasCover" class="absolute left-0 right-0 w-full flex items-center justify-center" :style="{ padding: placeholderCoverPadding + 'rem', bottom: authorBottom + 'rem' }">
|
<div v-if="!hasCover" class="absolute left-0 right-0 w-full flex items-center justify-center" :style="{ padding: placeholderCoverPadding + 'rem', bottom: authorBottom + 'rem' }">
|
||||||
<p class="text-center" style="color: rgb(247 223 187); opacity: 0.75" :style="{ fontSize: authorFontSize + 'rem' }">{{ authorCleaned }}</p>
|
<p class="text-center" style="color: rgb(247 223 187); opacity: 0.75" :style="{ fontSize: authorFontSize + 'rem' }">{{ authorCleaned }}</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<div v-if="showPlayButton" class="absolute -bottom-16 -right-16 rotate-45 w-32 h-32 p-2 bg-gradient-to-r from-transparent to-black to-40% inline-flex justify-start items-center">
|
||||||
|
<div class="hover:text-white text-gray-200 hover:scale-110 transform duration-200 pointer-events-auto -rotate-45" @click.stop.prevent="play">
|
||||||
|
<span class="material-icons" :style="{ fontSize: playIconFontSize + 'rem' }">{{ streamIsPlaying ? 'pause_circle' : 'play_circle_filled' }}</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Play/pause button for podcast episode -->
|
<!-- Play/pause button for podcast episode -->
|
||||||
@@ -75,9 +81,14 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Podcast Num Episodes -->
|
<!-- Podcast Num Episodes -->
|
||||||
<div v-else-if="numEpisodes && !isSelectionMode" class="absolute rounded-full bg-black bg-opacity-90 box-shadow-md z-10 flex items-center justify-center" :style="{ top: 0.375 * sizeMultiplier + 'rem', right: 0.375 * sizeMultiplier + 'rem', width: 1.25 * sizeMultiplier + 'rem', height: 1.25 * sizeMultiplier + 'rem' }">
|
<div v-else-if="numEpisodes && !numEpisodesIncomplete && !isSelectionMode" class="absolute rounded-full bg-black bg-opacity-90 box-shadow-md z-10 flex items-center justify-center" :style="{ top: 0.375 * sizeMultiplier + 'rem', right: 0.375 * sizeMultiplier + 'rem', width: 1.25 * sizeMultiplier + 'rem', height: 1.25 * sizeMultiplier + 'rem' }">
|
||||||
<p :style="{ fontSize: sizeMultiplier * 0.8 + 'rem' }">{{ numEpisodes }}</p>
|
<p :style="{ fontSize: sizeMultiplier * 0.8 + 'rem' }">{{ numEpisodes }}</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<!-- Podcast Num Episodes Incomplete -->
|
||||||
|
<div v-else-if="numEpisodesIncomplete && !isSelectionMode" class="absolute rounded-full bg-black bg-opacity-90 box-shadow-md z-10 flex items-center justify-center" :style="{ top: 0.375 * sizeMultiplier + 'rem', right: 0.375 * sizeMultiplier + 'rem', width: 1.25 * sizeMultiplier + 'rem', height: 1.25 * sizeMultiplier + 'rem' }">
|
||||||
|
<p :style="{ fontSize: sizeMultiplier * 0.8 + 'rem' }">{{ numEpisodesIncomplete }}</p>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
@@ -112,7 +123,6 @@ export default {
|
|||||||
isProcessingReadUpdate: false,
|
isProcessingReadUpdate: false,
|
||||||
libraryItem: null,
|
libraryItem: null,
|
||||||
imageReady: false,
|
imageReady: false,
|
||||||
rescanning: false,
|
|
||||||
selected: false,
|
selected: false,
|
||||||
isSelectionMode: false,
|
isSelectionMode: false,
|
||||||
showCoverBg: false,
|
showCoverBg: false,
|
||||||
@@ -180,6 +190,10 @@ export default {
|
|||||||
if (this.isLocal && this.isPodcast && this.media.episodes) return this.media.episodes.length
|
if (this.isLocal && this.isPodcast && this.media.episodes) return this.media.episodes.length
|
||||||
return this.media.numEpisodes
|
return this.media.numEpisodes
|
||||||
},
|
},
|
||||||
|
numEpisodesIncomplete() {
|
||||||
|
if (this.isLocal) return 0
|
||||||
|
return this._libraryItem.numEpisodesIncomplete || 0
|
||||||
|
},
|
||||||
processingBatch() {
|
processingBatch() {
|
||||||
return this.store.state.processingBatch
|
return this.store.state.processingBatch
|
||||||
},
|
},
|
||||||
@@ -306,14 +320,10 @@ export default {
|
|||||||
return this.localLibraryItem.media.episodes.find((ep) => ep.serverEpisodeId === this.recentEpisode.id)
|
return this.localLibraryItem.media.episodes.find((ep) => ep.serverEpisodeId === this.recentEpisode.id)
|
||||||
},
|
},
|
||||||
isStreaming() {
|
isStreaming() {
|
||||||
if (this.isPodcast) {
|
return this.store.getters['getIsMediaStreaming'](this.libraryItemId, this.recentEpisode?.id)
|
||||||
return this.$store.getters['getIsMediaStreaming'](this.libraryItemId, this.recentEpisode.id)
|
|
||||||
} else {
|
|
||||||
return false // not yet necessary for books
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
streamIsPlaying() {
|
streamIsPlaying() {
|
||||||
return this.$store.state.playerIsPlaying && this.isStreaming
|
return this.store.state.playerIsPlaying && this.isStreaming
|
||||||
},
|
},
|
||||||
isMissing() {
|
isMissing() {
|
||||||
return this._libraryItem.isMissing
|
return this._libraryItem.isMissing
|
||||||
@@ -393,6 +403,10 @@ export default {
|
|||||||
rssFeed() {
|
rssFeed() {
|
||||||
if (this.booksInSeries) return null
|
if (this.booksInSeries) return null
|
||||||
return this._libraryItem.rssFeed || null
|
return this._libraryItem.rssFeed || null
|
||||||
|
},
|
||||||
|
showPlayButton() {
|
||||||
|
return false
|
||||||
|
// return !this.isMissing && !this.isInvalid && !this.isStreaming && (this.numTracks || this.recentEpisode)
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
@@ -434,6 +448,7 @@ export default {
|
|||||||
// Server books may have a local library item
|
// Server books may have a local library item
|
||||||
this.localLibraryItem = localLibraryItem
|
this.localLibraryItem = localLibraryItem
|
||||||
},
|
},
|
||||||
|
async play() {},
|
||||||
async playEpisode() {
|
async playEpisode() {
|
||||||
await this.$hapticsImpact()
|
await this.$hapticsImpact()
|
||||||
const eventBus = this.$eventBus || this.$nuxt.$eventBus
|
const eventBus = this.$eventBus || this.$nuxt.$eventBus
|
||||||
@@ -472,29 +487,6 @@ export default {
|
|||||||
editClick() {
|
editClick() {
|
||||||
this.$emit('edit', this.libraryItem)
|
this.$emit('edit', this.libraryItem)
|
||||||
},
|
},
|
||||||
rescan() {
|
|
||||||
this.rescanning = true
|
|
||||||
this.$axios
|
|
||||||
.$get(`/api/items/${this.libraryItemId}/scan`)
|
|
||||||
.then((data) => {
|
|
||||||
this.rescanning = false
|
|
||||||
var result = data.result
|
|
||||||
if (!result) {
|
|
||||||
this.$toast.error(`Re-Scan Failed for "${this.title}"`)
|
|
||||||
} else if (result === 'UPDATED') {
|
|
||||||
this.$toast.success(`Re-Scan complete item was updated`)
|
|
||||||
} else if (result === 'UPTODATE') {
|
|
||||||
this.$toast.success(`Re-Scan complete item was up to date`)
|
|
||||||
} else if (result === 'REMOVED') {
|
|
||||||
this.$toast.error(`Re-Scan complete item was removed`)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.catch((error) => {
|
|
||||||
console.error('Failed to scan library item', error)
|
|
||||||
this.$toast.error('Failed to scan library item')
|
|
||||||
this.rescanning = false
|
|
||||||
})
|
|
||||||
},
|
|
||||||
showEditModalTracks() {
|
showEditModalTracks() {
|
||||||
// More menu func
|
// More menu func
|
||||||
this.store.commit('showEditModalOnTab', { libraryItem: this.libraryItem, tab: 'tracks' })
|
this.store.commit('showEditModalOnTab', { libraryItem: this.libraryItem, tab: 'tracks' })
|
||||||
|
|||||||
@@ -62,7 +62,6 @@ export default {
|
|||||||
isProcessingReadUpdate: false,
|
isProcessingReadUpdate: false,
|
||||||
libraryItem: null,
|
libraryItem: null,
|
||||||
imageReady: false,
|
imageReady: false,
|
||||||
rescanning: false,
|
|
||||||
selected: false,
|
selected: false,
|
||||||
isSelectionMode: false,
|
isSelectionMode: false,
|
||||||
showCoverBg: false,
|
showCoverBg: false,
|
||||||
@@ -349,29 +348,6 @@ export default {
|
|||||||
editClick() {
|
editClick() {
|
||||||
this.$emit('edit', this.libraryItem)
|
this.$emit('edit', this.libraryItem)
|
||||||
},
|
},
|
||||||
rescan() {
|
|
||||||
this.rescanning = true
|
|
||||||
this.$axios
|
|
||||||
.$get(`/api/items/${this.libraryItemId}/scan`)
|
|
||||||
.then((data) => {
|
|
||||||
this.rescanning = false
|
|
||||||
var result = data.result
|
|
||||||
if (!result) {
|
|
||||||
this.$toast.error(`Re-Scan Failed for "${this.title}"`)
|
|
||||||
} else if (result === 'UPDATED') {
|
|
||||||
this.$toast.success(`Re-Scan complete item was updated`)
|
|
||||||
} else if (result === 'UPTODATE') {
|
|
||||||
this.$toast.success(`Re-Scan complete item was up to date`)
|
|
||||||
} else if (result === 'REMOVED') {
|
|
||||||
this.$toast.error(`Re-Scan complete item was removed`)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.catch((error) => {
|
|
||||||
console.error('Failed to scan library item', error)
|
|
||||||
this.$toast.error('Failed to scan library item')
|
|
||||||
this.rescanning = false
|
|
||||||
})
|
|
||||||
},
|
|
||||||
showEditModalTracks() {
|
showEditModalTracks() {
|
||||||
// More menu func
|
// More menu func
|
||||||
this.store.commit('showEditModalOnTab', { libraryItem: this.libraryItem, tab: 'tracks' })
|
this.store.commit('showEditModalOnTab', { libraryItem: this.libraryItem, tab: 'tracks' })
|
||||||
|
|||||||
@@ -77,6 +77,7 @@
|
|||||||
|
|
||||||
<script>
|
<script>
|
||||||
import { Dialog } from '@capacitor/dialog'
|
import { Dialog } from '@capacitor/dialog'
|
||||||
|
import { CapacitorHttp } from '@capacitor/core'
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
data() {
|
data() {
|
||||||
@@ -217,27 +218,57 @@ export default {
|
|||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
pingServerAddress(address, customHeaders) {
|
async getRequest(url, headers, connectTimeout = 6000) {
|
||||||
const options = { timeout: 3000 }
|
const options = {
|
||||||
if (customHeaders) {
|
url,
|
||||||
options.headers = customHeaders
|
headers,
|
||||||
|
connectTimeout
|
||||||
}
|
}
|
||||||
return this.$axios
|
const response = await CapacitorHttp.get(options)
|
||||||
.$get(`${address}/ping`, options)
|
console.log('[ServerConnectForm] GET request response', response)
|
||||||
.then((data) => data.success)
|
if (response.status >= 400) {
|
||||||
|
throw new Error(response.data)
|
||||||
|
} else {
|
||||||
|
return response.data
|
||||||
|
}
|
||||||
|
},
|
||||||
|
async postRequest(url, data, headers, connectTimeout = 6000) {
|
||||||
|
if (!headers) headers = {}
|
||||||
|
if (!headers['Content-Type'] && data) {
|
||||||
|
headers['Content-Type'] = 'application/json'
|
||||||
|
}
|
||||||
|
const options = {
|
||||||
|
url,
|
||||||
|
headers,
|
||||||
|
data,
|
||||||
|
connectTimeout
|
||||||
|
}
|
||||||
|
const response = await CapacitorHttp.post(options)
|
||||||
|
console.log('[ServerConnectForm] POST request response', response)
|
||||||
|
if (response.status >= 400) {
|
||||||
|
throw new Error(response.data)
|
||||||
|
} else {
|
||||||
|
return response.data
|
||||||
|
}
|
||||||
|
},
|
||||||
|
pingServerAddress(address, customHeaders) {
|
||||||
|
return this.getRequest(`${address}/ping`, customHeaders)
|
||||||
|
.then((data) => {
|
||||||
|
return data.success
|
||||||
|
})
|
||||||
.catch((error) => {
|
.catch((error) => {
|
||||||
console.error('Server check failed', error)
|
console.error('Server ping failed', error)
|
||||||
|
const errorMsg = error.message || error
|
||||||
this.error = 'Failed to ping server'
|
this.error = 'Failed to ping server'
|
||||||
|
if (typeof errorMsg === 'string') {
|
||||||
|
this.error += ` (${errorMsg})`
|
||||||
|
}
|
||||||
|
|
||||||
return false
|
return false
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
requestServerLogin() {
|
requestServerLogin() {
|
||||||
const options = {}
|
return this.postRequest(`${this.serverConfig.address}/login`, { username: this.serverConfig.username, password: this.password }, this.serverConfig.customHeaders, 20000)
|
||||||
if (this.serverConfig.customHeaders) {
|
|
||||||
options.headers = this.serverConfig.customHeaders
|
|
||||||
}
|
|
||||||
return this.$axios
|
|
||||||
.$post(`${this.serverConfig.address}/login`, { username: this.serverConfig.username, password: this.password }, options)
|
|
||||||
.then((data) => {
|
.then((data) => {
|
||||||
if (!data.user) {
|
if (!data.user) {
|
||||||
console.error(data.error)
|
console.error(data.error)
|
||||||
@@ -248,8 +279,11 @@ export default {
|
|||||||
})
|
})
|
||||||
.catch((error) => {
|
.catch((error) => {
|
||||||
console.error('Server auth failed', error)
|
console.error('Server auth failed', error)
|
||||||
var errorMsg = error.response ? error.response.data || 'Unknown Error' : 'Unknown Error'
|
const errorMsg = error.message || error
|
||||||
this.error = errorMsg
|
this.error = 'Failed to login'
|
||||||
|
if (typeof errorMsg === 'string') {
|
||||||
|
this.error += ` (${errorMsg})`
|
||||||
|
}
|
||||||
return false
|
return false
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
@@ -330,12 +364,19 @@ export default {
|
|||||||
|
|
||||||
this.error = null
|
this.error = null
|
||||||
this.processing = true
|
this.processing = true
|
||||||
var authRes = await this.$axios.$post(`${this.serverConfig.address}/api/authorize`, null, { headers: { Authorization: `Bearer ${this.serverConfig.token}` } }).catch((error) => {
|
|
||||||
console.error('[Server] Server auth failed', error)
|
const authRes = await this.postRequest(`${this.serverConfig.address}/api/authorize`, null, { Authorization: `Bearer ${this.serverConfig.token}` }).catch((error) => {
|
||||||
var errorMsg = error.response ? error.response.data || 'Unknown Error' : 'Unknown Error'
|
console.error('[ServerConnectForm] Server auth failed', error)
|
||||||
this.error = errorMsg
|
const errorMsg = error.message || error
|
||||||
|
this.error = 'Failed to authorize'
|
||||||
|
if (typeof errorMsg === 'string') {
|
||||||
|
this.error += ` (${errorMsg})`
|
||||||
|
}
|
||||||
return false
|
return false
|
||||||
})
|
})
|
||||||
|
|
||||||
|
console.log('[ServerConnectForm] authRes=', authRes)
|
||||||
|
|
||||||
this.processing = false
|
this.processing = false
|
||||||
return authRes
|
return authRes
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -140,8 +140,8 @@ export default {
|
|||||||
console.log('Podcast payload', podcastPayload)
|
console.log('Podcast payload', podcastPayload)
|
||||||
|
|
||||||
this._processing = true
|
this._processing = true
|
||||||
this.$axios
|
this.$nativeHttp
|
||||||
.$post('/api/podcasts', podcastPayload)
|
.post('/api/podcasts', podcastPayload)
|
||||||
.then((libraryItem) => {
|
.then((libraryItem) => {
|
||||||
this._processing = false
|
this._processing = false
|
||||||
this.$toast.success('Podcast added')
|
this.$toast.success('Podcast added')
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
<template>
|
<template>
|
||||||
<modals-modal v-model="show" :width="300" height="100%">
|
<modals-modal v-model="show" :width="400" height="100%">
|
||||||
<template #outer>
|
<template #outer>
|
||||||
<div class="absolute top-8 left-4 z-40">
|
<div class="absolute top-8 left-4 z-40">
|
||||||
<p class="text-white text-2xl truncate">Bookmarks</p>
|
<p class="text-white text-2xl truncate">Bookmarks</p>
|
||||||
@@ -102,8 +102,8 @@ export default {
|
|||||||
})
|
})
|
||||||
if (!value) return
|
if (!value) return
|
||||||
|
|
||||||
this.$axios
|
this.$nativeHttp
|
||||||
.$delete(`/api/me/item/${this.libraryItemId}/bookmark/${bm.time}`)
|
.delete(`/api/me/item/${this.libraryItemId}/bookmark/${bm.time}`)
|
||||||
.then(() => {
|
.then(() => {
|
||||||
this.$toast.success('Bookmark removed')
|
this.$toast.success('Bookmark removed')
|
||||||
})
|
})
|
||||||
@@ -119,8 +119,8 @@ export default {
|
|||||||
},
|
},
|
||||||
submitUpdateBookmark(updatedBookmark) {
|
submitUpdateBookmark(updatedBookmark) {
|
||||||
var bookmark = { ...updatedBookmark }
|
var bookmark = { ...updatedBookmark }
|
||||||
this.$axios
|
this.$nativeHttp
|
||||||
.$patch(`/api/me/item/${this.libraryItemId}/bookmark`, bookmark)
|
.patch(`/api/me/item/${this.libraryItemId}/bookmark`, bookmark)
|
||||||
.then(() => {
|
.then(() => {
|
||||||
this.$toast.success('Bookmark updated')
|
this.$toast.success('Bookmark updated')
|
||||||
})
|
})
|
||||||
@@ -134,12 +134,12 @@ export default {
|
|||||||
if (!this.newBookmarkTitle) {
|
if (!this.newBookmarkTitle) {
|
||||||
this.newBookmarkTitle = this.$formatDate(Date.now(), 'MMM dd, yyyy HH:mm')
|
this.newBookmarkTitle = this.$formatDate(Date.now(), 'MMM dd, yyyy HH:mm')
|
||||||
}
|
}
|
||||||
var bookmark = {
|
const bookmark = {
|
||||||
title: this.newBookmarkTitle,
|
title: this.newBookmarkTitle,
|
||||||
time: Math.floor(this.currentTime)
|
time: Math.floor(this.currentTime)
|
||||||
}
|
}
|
||||||
this.$axios
|
this.$nativeHttp
|
||||||
.$post(`/api/me/item/${this.libraryItemId}/bookmark`, bookmark)
|
.post(`/api/me/item/${this.libraryItemId}/bookmark`, bookmark)
|
||||||
.then(() => {
|
.then(() => {
|
||||||
this.$toast.success('Bookmark added')
|
this.$toast.success('Bookmark added')
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -96,8 +96,8 @@ export default {
|
|||||||
}
|
}
|
||||||
|
|
||||||
this.processing = true
|
this.processing = true
|
||||||
this.$axios
|
this.$nativeHttp
|
||||||
.$post(`/api/podcasts/${this.libraryItem.id}/download-episodes`, episodesToDownload)
|
.post(`/api/podcasts/${this.libraryItem.id}/download-episodes`, episodesToDownload)
|
||||||
.then(() => {
|
.then(() => {
|
||||||
this.processing = false
|
this.processing = false
|
||||||
this.$toast.success('Started downloading episodes on server')
|
this.$toast.success('Started downloading episodes on server')
|
||||||
|
|||||||
@@ -1,15 +1,17 @@
|
|||||||
<template>
|
<template>
|
||||||
<div :key="bookmark.id" :id="`bookmark-row-${bookmark.id}`" class="flex items-center px-3 py-4 justify-start cursor-pointer hover:bg-bg relative" :class="highlight ? 'bg-bg bg-opacity-60' : ' bg-opacity-20'" @click="click">
|
<div :key="bookmark.id" :id="`bookmark-row-${bookmark.id}`" class="flex items-center px-1 py-4 justify-start cursor-pointer hover:bg-bg relative" :class="highlight ? 'bg-bg bg-opacity-60' : ' bg-opacity-20'" @click="click">
|
||||||
<span class="material-icons text-xl" :class="highlight ? 'text-success' : 'text-white text-opacity-60'">{{ highlight ? 'bookmark' : 'bookmark_border' }}</span>
|
<div class="flex-grow overflow-hidden px-2">
|
||||||
<div class="flex-grow overflow-hidden">
|
<div class="flex items-center mb-0.5">
|
||||||
<p class="pl-2 pr-2 truncate text-sm">{{ bookmark.title }}</p>
|
<i class="material-icons text-lg pr-1 -mb-1" :class="highlight ? 'text-success' : 'text-white/60'">{{ highlight ? 'bookmark' : 'bookmark_border' }}</i>
|
||||||
|
<p class="truncate text-sm">
|
||||||
|
{{ bookmark.title }}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
<p class="text-sm font-mono text-gray-300 flex items-center"><span class="material-icons text-base text-white/60 pl-px pr-1">schedule</span>{{ $secondsToTimestamp(bookmark.time) }}</p>
|
||||||
</div>
|
</div>
|
||||||
<div class="h-full flex items-center w-14 justify-end">
|
<div class="h-full flex items-center justify-end transform w-16 pr-2" @click.stop>
|
||||||
<span class="font-mono text-xs text-gray-300">{{ $secondsToTimestamp(bookmark.time) }}</span>
|
<span class="material-icons text-2xl mr-2 text-gray-200 hover:text-yellow-400" @click.stop="editClick">edit</span>
|
||||||
</div>
|
<span class="material-icons text-2xl text-gray-200 hover:text-error cursor-pointer" @click.stop="deleteClick">delete</span>
|
||||||
<div class="h-full flex items-center justify-end transform w-16">
|
|
||||||
<span class="material-icons text-lg mr-2 text-gray-200 hover:text-yellow-400" @click.stop="editClick">edit</span>
|
|
||||||
<span class="material-icons text-lg text-gray-200 hover:text-error cursor-pointer" @click.stop="deleteClick">delete</span>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|||||||
@@ -109,8 +109,8 @@ export default {
|
|||||||
},
|
},
|
||||||
loadPlaylists() {
|
loadPlaylists() {
|
||||||
this.loading = true
|
this.loading = true
|
||||||
this.$axios
|
this.$nativeHttp
|
||||||
.$get(`/api/libraries/${this.currentLibraryId}/playlists`)
|
.get(`/api/libraries/${this.currentLibraryId}/playlists`)
|
||||||
.then((data) => {
|
.then((data) => {
|
||||||
this.playlists = data.results || []
|
this.playlists = data.results || []
|
||||||
})
|
})
|
||||||
@@ -135,8 +135,8 @@ export default {
|
|||||||
this.processing = true
|
this.processing = true
|
||||||
|
|
||||||
const itemObjects = this.selectedPlaylistItems.map((pi) => ({ libraryItemId: pi.libraryItem.id, episodeId: pi.episode ? pi.episode.id : null }))
|
const itemObjects = this.selectedPlaylistItems.map((pi) => ({ libraryItemId: pi.libraryItem.id, episodeId: pi.episode ? pi.episode.id : null }))
|
||||||
this.$axios
|
this.$nativeHttp
|
||||||
.$post(`/api/playlists/${playlist.id}/batch/remove`, { items: itemObjects })
|
.post(`/api/playlists/${playlist.id}/batch/remove`, { items: itemObjects })
|
||||||
.then((updatedPlaylist) => {
|
.then((updatedPlaylist) => {
|
||||||
console.log(`Items removed from playlist`, updatedPlaylist)
|
console.log(`Items removed from playlist`, updatedPlaylist)
|
||||||
})
|
})
|
||||||
@@ -153,8 +153,8 @@ export default {
|
|||||||
this.processing = true
|
this.processing = true
|
||||||
|
|
||||||
const itemObjects = this.selectedPlaylistItems.map((pi) => ({ libraryItemId: pi.libraryItem.id, episodeId: pi.episode ? pi.episode.id : null }))
|
const itemObjects = this.selectedPlaylistItems.map((pi) => ({ libraryItemId: pi.libraryItem.id, episodeId: pi.episode ? pi.episode.id : null }))
|
||||||
this.$axios
|
this.$nativeHttp
|
||||||
.$post(`/api/playlists/${playlist.id}/batch/add`, { items: itemObjects })
|
.post(`/api/playlists/${playlist.id}/batch/add`, { items: itemObjects })
|
||||||
.then((updatedPlaylist) => {
|
.then((updatedPlaylist) => {
|
||||||
console.log(`Items added to playlist`, updatedPlaylist)
|
console.log(`Items added to playlist`, updatedPlaylist)
|
||||||
})
|
})
|
||||||
@@ -184,8 +184,8 @@ export default {
|
|||||||
name: this.newPlaylistName
|
name: this.newPlaylistName
|
||||||
}
|
}
|
||||||
|
|
||||||
this.$axios
|
this.$nativeHttp
|
||||||
.$post('/api/playlists', newPlaylist)
|
.post('/api/playlists', newPlaylist)
|
||||||
.then((data) => {
|
.then((data) => {
|
||||||
console.log('New playlist created', data)
|
console.log('New playlist created', data)
|
||||||
this.newPlaylistName = ''
|
this.newPlaylistName = ''
|
||||||
|
|||||||
@@ -145,8 +145,8 @@ export default {
|
|||||||
}
|
}
|
||||||
|
|
||||||
console.log('Payload', payload)
|
console.log('Payload', payload)
|
||||||
this.$axios
|
this.$nativeHttp
|
||||||
.$post(`/api/feeds/${this.entityType}/${this.entityId}/open`, payload)
|
.post(`/api/feeds/${this.entityType}/${this.entityId}/open`, payload)
|
||||||
.then((data) => {
|
.then((data) => {
|
||||||
console.log('Opened RSS Feed', data)
|
console.log('Opened RSS Feed', data)
|
||||||
this.currentFeed = data.feed
|
this.currentFeed = data.feed
|
||||||
@@ -163,8 +163,8 @@ export default {
|
|||||||
},
|
},
|
||||||
closeFeed() {
|
closeFeed() {
|
||||||
this.processing = true
|
this.processing = true
|
||||||
this.$axios
|
this.$nativeHttp
|
||||||
.$post(`/api/feeds/${this.currentFeed.id}/close`)
|
.post(`/api/feeds/${this.currentFeed.id}/close`)
|
||||||
.then(() => {
|
.then(() => {
|
||||||
this.$toast.success(this.$strings.ToastRSSFeedCloseSuccess)
|
this.$toast.success(this.$strings.ToastRSSFeedCloseSuccess)
|
||||||
this.show = false
|
this.show = false
|
||||||
|
|||||||
@@ -183,7 +183,7 @@ export default {
|
|||||||
|
|
||||||
// Update server item
|
// Update server item
|
||||||
if (this.serverLibraryItemId) {
|
if (this.serverLibraryItemId) {
|
||||||
this.$axios.$patch(`/api/me/progress/${this.serverLibraryItemId}`, payload).catch((error) => {
|
this.$nativeHttp.patch(`/api/me/progress/${this.serverLibraryItemId}`, payload).catch((error) => {
|
||||||
console.error('ComicReader.updateProgress failed:', error)
|
console.error('ComicReader.updateProgress failed:', error)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@@ -335,8 +335,8 @@ export default {
|
|||||||
},
|
},
|
||||||
parseImageFilename(filename) {
|
parseImageFilename(filename) {
|
||||||
var basename = Path.basename(filename, Path.extname(filename))
|
var basename = Path.basename(filename, Path.extname(filename))
|
||||||
var numbersinpath = basename.match(/\d{1,5}/g)
|
var numbersinpath = basename.match(/\d+/g)
|
||||||
if (!numbersinpath || !numbersinpath.length) {
|
if (!numbersinpath?.length) {
|
||||||
return {
|
return {
|
||||||
index: -1,
|
index: -1,
|
||||||
filename
|
filename
|
||||||
|
|||||||
@@ -3,6 +3,7 @@
|
|||||||
<div id="viewer" class="h-full w-full"></div>
|
<div id="viewer" class="h-full w-full"></div>
|
||||||
|
|
||||||
<div class="fixed left-0 h-8 w-full px-4 flex items-center" :class="isLightTheme ? 'bg-white text-black' : 'bg-primary text-white/80'" :style="{ bottom: isPlayerOpen ? '120px' : '0px' }">
|
<div class="fixed left-0 h-8 w-full px-4 flex items-center" :class="isLightTheme ? 'bg-white text-black' : 'bg-primary text-white/80'" :style="{ bottom: isPlayerOpen ? '120px' : '0px' }">
|
||||||
|
<p v-if="totalLocations" class="text-xs text-slate-600">Location {{ currentLocationNum }} of {{ totalLocations }}</p>
|
||||||
<div class="flex-grow" />
|
<div class="flex-grow" />
|
||||||
<p class="text-xs">{{ progress }}%</p>
|
<p class="text-xs">{{ progress }}%</p>
|
||||||
</div>
|
</div>
|
||||||
@@ -29,6 +30,10 @@ export default {
|
|||||||
/** @type {ePub.Rendition} */
|
/** @type {ePub.Rendition} */
|
||||||
rendition: null,
|
rendition: null,
|
||||||
progress: 0,
|
progress: 0,
|
||||||
|
totalLocations: 0,
|
||||||
|
currentLocationNum: 0,
|
||||||
|
currentLocationCfi: null,
|
||||||
|
inittingDisplay: true,
|
||||||
ereaderSettings: {
|
ereaderSettings: {
|
||||||
theme: 'dark',
|
theme: 'dark',
|
||||||
fontScale: 100,
|
fontScale: 100,
|
||||||
@@ -103,15 +108,11 @@ export default {
|
|||||||
const fontColor = isDark ? '#fff' : '#000'
|
const fontColor = isDark ? '#fff' : '#000'
|
||||||
const backgroundColor = isDark ? 'rgb(35 35 35)' : 'rgb(255, 255, 255)'
|
const backgroundColor = isDark ? 'rgb(35 35 35)' : 'rgb(255, 255, 255)'
|
||||||
|
|
||||||
const lineSpacing = this.ereaderSettings.lineSpacing / 100
|
|
||||||
|
|
||||||
const fontScale = this.ereaderSettings.fontScale / 100
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
'*': {
|
'*': {
|
||||||
color: `${fontColor}!important`,
|
color: `${fontColor}!important`,
|
||||||
'background-color': `${backgroundColor}!important`,
|
'background-color': `${backgroundColor}!important`,
|
||||||
'line-height': lineSpacing * fontScale + 'rem!important'
|
'line-height': this.ereaderSettings.lineSpacing + '%!important'
|
||||||
},
|
},
|
||||||
a: {
|
a: {
|
||||||
color: `${fontColor}!important`
|
color: `${fontColor}!important`
|
||||||
@@ -171,7 +172,7 @@ export default {
|
|||||||
|
|
||||||
// Update server item
|
// Update server item
|
||||||
if (this.serverLibraryItemId) {
|
if (this.serverLibraryItemId) {
|
||||||
this.$axios.$patch(`/api/me/progress/${this.serverLibraryItemId}`, payload).catch((error) => {
|
this.$nativeHttp.patch(`/api/me/progress/${this.serverLibraryItemId}`, payload).catch((error) => {
|
||||||
console.error('EpubReader.updateProgress failed:', error)
|
console.error('EpubReader.updateProgress failed:', error)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@@ -263,9 +264,20 @@ export default {
|
|||||||
},
|
},
|
||||||
/** @param {string} location - CFI of the new location */
|
/** @param {string} location - CFI of the new location */
|
||||||
relocated(location) {
|
relocated(location) {
|
||||||
if (this.savedEbookLocation === location.start.cfi) {
|
console.log(`[EpubReader] relocated ${location.start.cfi}`)
|
||||||
|
if (this.inittingDisplay) {
|
||||||
|
console.log(`[EpubReader] relocated but initting display ${location.start.cfi}`)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
this.currentLocationNum = location.start.location
|
||||||
|
|
||||||
|
if (this.currentLocationCfi === location.start.cfi) {
|
||||||
|
console.log(`[EpubReader] location already saved`, location.start.cfi)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log(`[EpubReader] Saving new location ${location.start.cfi}`)
|
||||||
|
this.currentLocationCfi = location.start.cfi
|
||||||
|
|
||||||
if (location.end.percentage) {
|
if (location.end.percentage) {
|
||||||
this.updateProgress({
|
this.updateProgress({
|
||||||
@@ -284,7 +296,7 @@ export default {
|
|||||||
|
|
||||||
/** @type {EpubReader} */
|
/** @type {EpubReader} */
|
||||||
const reader = this
|
const reader = this
|
||||||
console.log('initEpub', reader.url)
|
console.log('[EpubReader] initEpub', reader.url)
|
||||||
/** @type {ePub.Book} */
|
/** @type {ePub.Book} */
|
||||||
reader.book = new ePub(reader.url, {
|
reader.book = new ePub(reader.url, {
|
||||||
width: window.innerWidth,
|
width: window.innerWidth,
|
||||||
@@ -305,23 +317,34 @@ export default {
|
|||||||
})
|
})
|
||||||
|
|
||||||
reader.book.ready.then(() => {
|
reader.book.ready.then(() => {
|
||||||
// load saved progress
|
console.log('%c [EpubReader] Book ready', 'color:cyan;')
|
||||||
// when not checking spine first uncaught exception is thrown
|
|
||||||
|
let displayCfi = reader.book.locations.start
|
||||||
if (this.savedEbookLocation && reader.book.spine.get(this.savedEbookLocation)) {
|
if (this.savedEbookLocation && reader.book.spine.get(this.savedEbookLocation)) {
|
||||||
reader.rendition.display(this.savedEbookLocation)
|
displayCfi = this.savedEbookLocation
|
||||||
} else {
|
|
||||||
reader.rendition.display(reader.book.locations.start)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
reader.rendition.on('rendered', () => {
|
reader.rendition.on('displayed', async () => {
|
||||||
|
console.log('%c [EpubReader] Rendition displayed', 'color:blue;')
|
||||||
|
|
||||||
|
// Overriding the needsSnap function in epubjs `snap.js` to fix a bug with scrollLeft being a decimal
|
||||||
|
reader.rendition.manager.snapper.needsSnap = function () {
|
||||||
|
let left = Math.round(this.scrollLeft)
|
||||||
|
let snapWidth = this.layout.pageWidth * this.layout.divisor
|
||||||
|
return left % snapWidth !== 0
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
reader.rendition.on('rendered', (section, view) => {
|
||||||
this.applyTheme()
|
this.applyTheme()
|
||||||
|
console.log('%c [EpubReader] Rendition rendered', 'color:red;', section, view)
|
||||||
})
|
})
|
||||||
|
|
||||||
// set up event listeners
|
// set up event listeners
|
||||||
reader.rendition.on('relocated', reader.relocated)
|
reader.rendition.on('relocated', reader.relocated)
|
||||||
|
|
||||||
reader.rendition.on('displayError', (err) => {
|
reader.rendition.on('displayError', (err) => {
|
||||||
console.log('Display error', err)
|
console.log('[EpubReader] Display error', err)
|
||||||
})
|
})
|
||||||
|
|
||||||
reader.rendition.on('touchstart', (event) => {
|
reader.rendition.on('touchstart', (event) => {
|
||||||
@@ -335,11 +358,23 @@ export default {
|
|||||||
const savedLocations = this.loadLocations()
|
const savedLocations = this.loadLocations()
|
||||||
if (savedLocations) {
|
if (savedLocations) {
|
||||||
reader.book.locations.load(savedLocations)
|
reader.book.locations.load(savedLocations)
|
||||||
|
this.totalLocations = reader.book.locations.length()
|
||||||
} else {
|
} else {
|
||||||
reader.book.locations.generate().then(() => {
|
reader.book.locations.generate(100).then(() => {
|
||||||
|
this.totalLocations = reader.book.locations.length()
|
||||||
|
this.currentLocationNum = reader.rendition.currentLocation()?.start.location || 0
|
||||||
this.checkSaveLocations(reader.book.locations.save())
|
this.checkSaveLocations(reader.book.locations.save())
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TODO: To get the correct page need to render twice. On book ready and after first display. Figure out why
|
||||||
|
console.log(`[EpubReader] Displaying cfi ${displayCfi}`)
|
||||||
|
this.currentLocationCfi = displayCfi
|
||||||
|
reader.rendition.display(displayCfi).then(() => {
|
||||||
|
reader.rendition.display(displayCfi).then(() => {
|
||||||
|
this.inittingDisplay = false
|
||||||
|
})
|
||||||
|
})
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
applyTheme() {
|
applyTheme() {
|
||||||
|
|||||||
@@ -146,7 +146,7 @@ export default {
|
|||||||
|
|
||||||
// Update server item
|
// Update server item
|
||||||
if (this.serverLibraryItemId) {
|
if (this.serverLibraryItemId) {
|
||||||
this.$axios.$patch(`/api/me/progress/${this.serverLibraryItemId}`, payload).catch((error) => {
|
this.$nativeHttp.patch(`/api/me/progress/${this.serverLibraryItemId}`, payload).catch((error) => {
|
||||||
console.error('PdfReader.updateProgress failed:', error)
|
console.error('PdfReader.updateProgress failed:', error)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -282,14 +282,13 @@ export default {
|
|||||||
// Touch must be less than 1s. Must be > 60px drag and X distance > Y distance
|
// Touch must be less than 1s. Must be > 60px drag and X distance > Y distance
|
||||||
const touchTimeMs = Date.now() - this.touchstartTime
|
const touchTimeMs = Date.now() - this.touchstartTime
|
||||||
if (touchTimeMs >= 1000) {
|
if (touchTimeMs >= 1000) {
|
||||||
console.log('Touch too long', touchTimeMs)
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
const touchDistanceX = Math.abs(this.touchendX - this.touchstartX)
|
const touchDistanceX = Math.abs(this.touchendX - this.touchstartX)
|
||||||
const touchDistanceY = Math.abs(this.touchendY - this.touchstartY)
|
const touchDistanceY = Math.abs(this.touchendY - this.touchstartY)
|
||||||
const touchDistance = Math.sqrt(Math.pow(this.touchstartX - this.touchendX, 2) + Math.pow(this.touchstartY - this.touchendY, 2))
|
const touchDistance = Math.sqrt(Math.pow(this.touchstartX - this.touchendX, 2) + Math.pow(this.touchstartY - this.touchendY, 2))
|
||||||
if (touchDistance < 60) {
|
if (touchDistance < 30) {
|
||||||
if (this.showSettingsModal) {
|
if (this.showSettingsModal) {
|
||||||
this.showSettingsModal = false
|
this.showSettingsModal = false
|
||||||
} else {
|
} else {
|
||||||
@@ -302,11 +301,13 @@ export default {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
this.hideToolbar()
|
this.hideToolbar()
|
||||||
if (this.touchendX < this.touchstartX) {
|
if (!this.isEpub) {
|
||||||
this.next()
|
if (this.touchendX < this.touchstartX) {
|
||||||
}
|
this.next()
|
||||||
if (this.touchendX > this.touchstartX) {
|
}
|
||||||
this.prev()
|
if (this.touchendX > this.touchstartX) {
|
||||||
|
this.prev()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
showToolbar() {
|
showToolbar() {
|
||||||
|
|||||||
@@ -83,8 +83,8 @@ export default {
|
|||||||
},
|
},
|
||||||
updateEbookStatus() {
|
updateEbookStatus() {
|
||||||
this.processing = true
|
this.processing = true
|
||||||
this.$axios
|
this.$nativeHttp
|
||||||
.$patch(`/api/items/${this.libraryItemId}/ebook/${this.selectedFile.ino}/status`)
|
.patch(`/api/items/${this.libraryItemId}/ebook/${this.selectedFile.ino}/status`)
|
||||||
.then(() => {
|
.then(() => {
|
||||||
this.$toast.success('Ebook updated')
|
this.$toast.success('Ebook updated')
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -280,8 +280,8 @@ export default {
|
|||||||
const updatePayload = {
|
const updatePayload = {
|
||||||
isFinished: !this.userIsFinished
|
isFinished: !this.userIsFinished
|
||||||
}
|
}
|
||||||
this.$axios
|
this.$nativeHttp
|
||||||
.$patch(`/api/me/progress/${this.libraryItemId}/${this.episode.id}`, updatePayload)
|
.patch(`/api/me/progress/${this.libraryItemId}/${this.episode.id}`, updatePayload)
|
||||||
.catch((error) => {
|
.catch((error) => {
|
||||||
console.error('Failed', error)
|
console.error('Failed', error)
|
||||||
this.$toast.error(`Failed to mark as ${updatePayload.isFinished ? 'Finished' : 'Not Finished'}`)
|
this.$toast.error(`Failed to mark as ${updatePayload.isFinished ? 'Finished' : 'Not Finished'}`)
|
||||||
|
|||||||
@@ -209,8 +209,8 @@ export default {
|
|||||||
})
|
})
|
||||||
|
|
||||||
if (value) {
|
if (value) {
|
||||||
this.$axios
|
this.$nativeHttp
|
||||||
.$get(`/api/podcasts/${this.libraryItemId}/clear-queue`)
|
.get(`/api/podcasts/${this.libraryItemId}/clear-queue`)
|
||||||
.then(() => {
|
.then(() => {
|
||||||
this.$toast.success('Episode download queue cleared')
|
this.$toast.success('Episode download queue cleared')
|
||||||
this.episodeDownloadQueued = []
|
this.episodeDownloadQueued = []
|
||||||
@@ -230,7 +230,7 @@ export default {
|
|||||||
return this.$toast.error('Podcast does not have an RSS Feed')
|
return this.$toast.error('Podcast does not have an RSS Feed')
|
||||||
}
|
}
|
||||||
this.fetchingRSSFeed = true
|
this.fetchingRSSFeed = true
|
||||||
const payload = await this.$axios.$post(`/api/podcasts/feed`, { rssFeed: this.mediaMetadata.feedUrl }).catch((error) => {
|
const payload = await this.$nativeHttp.post(`/api/podcasts/feed`, { rssFeed: this.mediaMetadata.feedUrl }).catch((error) => {
|
||||||
console.error('Failed to get feed', error)
|
console.error('Failed to get feed', error)
|
||||||
this.$toast.error('Failed to get podcast feed')
|
this.$toast.error('Failed to get podcast feed')
|
||||||
return null
|
return null
|
||||||
|
|||||||
@@ -286,8 +286,8 @@ export default {
|
|||||||
const updatePayload = {
|
const updatePayload = {
|
||||||
isFinished: !this.userIsFinished
|
isFinished: !this.userIsFinished
|
||||||
}
|
}
|
||||||
this.$axios
|
this.$nativeHttp
|
||||||
.$patch(`/api/me/progress/${this.libraryItemId}/${this.episode.id}`, updatePayload)
|
.patch(`/api/me/progress/${this.libraryItemId}/${this.episode.id}`, updatePayload)
|
||||||
.catch((error) => {
|
.catch((error) => {
|
||||||
console.error('Failed', error)
|
console.error('Failed', error)
|
||||||
this.$toast.error(`Failed to mark as ${updatePayload.isFinished ? 'Finished' : 'Not Finished'}`)
|
this.$toast.error(`Failed to mark as ${updatePayload.isFinished ? 'Finished' : 'Not Finished'}`)
|
||||||
|
|||||||
@@ -34,7 +34,7 @@ export default {
|
|||||||
return this.$store.state.attemptingConnection
|
return this.$store.state.attemptingConnection
|
||||||
},
|
},
|
||||||
icon() {
|
icon() {
|
||||||
if (!this.user) return null // hide when not connected to server
|
if (!this.user && !this.attemptingConnection) return null // hide when not connected to server
|
||||||
|
|
||||||
if (this.attemptingConnection) {
|
if (this.attemptingConnection) {
|
||||||
return 'cloud_sync'
|
return 'cloud_sync'
|
||||||
|
|||||||
+1
-1
@@ -1,9 +1,9 @@
|
|||||||
App/build
|
App/build
|
||||||
App/Pods
|
App/Pods
|
||||||
App/Podfile.lock
|
|
||||||
App/App/public
|
App/App/public
|
||||||
DerivedData
|
DerivedData
|
||||||
xcuserdata
|
xcuserdata
|
||||||
|
|
||||||
# Cordova plugins for Capacitor
|
# Cordova plugins for Capacitor
|
||||||
capacitor-cordova-ios-plugins
|
capacitor-cordova-ios-plugins
|
||||||
|
|
||||||
|
|||||||
@@ -724,12 +724,12 @@
|
|||||||
ASSETCATALOG_COMPILER_APPICON_NAME = Icons;
|
ASSETCATALOG_COMPILER_APPICON_NAME = Icons;
|
||||||
CLANG_ENABLE_MODULES = YES;
|
CLANG_ENABLE_MODULES = YES;
|
||||||
CODE_SIGN_STYLE = Automatic;
|
CODE_SIGN_STYLE = Automatic;
|
||||||
CURRENT_PROJECT_VERSION = 22;
|
CURRENT_PROJECT_VERSION = 23;
|
||||||
DEVELOPMENT_TEAM = 7UFJ7D8V6A;
|
DEVELOPMENT_TEAM = 7UFJ7D8V6A;
|
||||||
INFOPLIST_FILE = App/Info.plist;
|
INFOPLIST_FILE = App/Info.plist;
|
||||||
IPHONEOS_DEPLOYMENT_TARGET = 14.0;
|
IPHONEOS_DEPLOYMENT_TARGET = 14.0;
|
||||||
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks";
|
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks";
|
||||||
MARKETING_VERSION = 0.9.65;
|
MARKETING_VERSION = 0.9.66;
|
||||||
OTHER_SWIFT_FLAGS = "$(inherited) \"-D\" \"COCOAPODS\" \"-DDEBUG\"";
|
OTHER_SWIFT_FLAGS = "$(inherited) \"-D\" \"COCOAPODS\" \"-DDEBUG\"";
|
||||||
PRODUCT_BUNDLE_IDENTIFIER = com.audiobookshelf.app.dev;
|
PRODUCT_BUNDLE_IDENTIFIER = com.audiobookshelf.app.dev;
|
||||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||||
@@ -748,12 +748,12 @@
|
|||||||
ASSETCATALOG_COMPILER_APPICON_NAME = Icons;
|
ASSETCATALOG_COMPILER_APPICON_NAME = Icons;
|
||||||
CLANG_ENABLE_MODULES = YES;
|
CLANG_ENABLE_MODULES = YES;
|
||||||
CODE_SIGN_STYLE = Automatic;
|
CODE_SIGN_STYLE = Automatic;
|
||||||
CURRENT_PROJECT_VERSION = 22;
|
CURRENT_PROJECT_VERSION = 23;
|
||||||
DEVELOPMENT_TEAM = 7UFJ7D8V6A;
|
DEVELOPMENT_TEAM = 7UFJ7D8V6A;
|
||||||
INFOPLIST_FILE = App/Info.plist;
|
INFOPLIST_FILE = App/Info.plist;
|
||||||
IPHONEOS_DEPLOYMENT_TARGET = 14.0;
|
IPHONEOS_DEPLOYMENT_TARGET = 14.0;
|
||||||
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks";
|
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks";
|
||||||
MARKETING_VERSION = 0.9.65;
|
MARKETING_VERSION = 0.9.66;
|
||||||
PRODUCT_BUNDLE_IDENTIFIER = com.audiobookshelf.app;
|
PRODUCT_BUNDLE_IDENTIFIER = com.audiobookshelf.app;
|
||||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||||
SWIFT_ACTIVE_COMPILATION_CONDITIONS = "";
|
SWIFT_ACTIVE_COMPILATION_CONDITIONS = "";
|
||||||
|
|||||||
@@ -2,5 +2,10 @@
|
|||||||
"appId": "com.audiobookshelf.app",
|
"appId": "com.audiobookshelf.app",
|
||||||
"appName": "audiobookshelf-app",
|
"appName": "audiobookshelf-app",
|
||||||
"webDir": "dist",
|
"webDir": "dist",
|
||||||
"bundledWebRuntime": false
|
"bundledWebRuntime": false,
|
||||||
|
"plugins": {
|
||||||
|
"CapacitorHttp": {
|
||||||
|
"enabled": false
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,87 @@
|
|||||||
|
PODS:
|
||||||
|
- Alamofire (5.6.4)
|
||||||
|
- Capacitor (4.8.0):
|
||||||
|
- CapacitorCordova
|
||||||
|
- CapacitorApp (4.1.1):
|
||||||
|
- Capacitor
|
||||||
|
- CapacitorClipboard (4.1.0):
|
||||||
|
- Capacitor
|
||||||
|
- CapacitorCordova (4.8.0)
|
||||||
|
- CapacitorDialog (4.1.0):
|
||||||
|
- Capacitor
|
||||||
|
- CapacitorHaptics (4.1.0):
|
||||||
|
- Capacitor
|
||||||
|
- CapacitorNetwork (4.1.0):
|
||||||
|
- Capacitor
|
||||||
|
- CapacitorPreferences (4.0.2):
|
||||||
|
- Capacitor
|
||||||
|
- CapacitorStatusBar (4.1.1):
|
||||||
|
- Capacitor
|
||||||
|
- CordovaPlugins (4.8.0):
|
||||||
|
- CapacitorCordova
|
||||||
|
- Realm (10.36.0):
|
||||||
|
- Realm/Headers (= 10.36.0)
|
||||||
|
- Realm/Headers (10.36.0)
|
||||||
|
- RealmSwift (10.36.0):
|
||||||
|
- Realm (= 10.36.0)
|
||||||
|
|
||||||
|
DEPENDENCIES:
|
||||||
|
- Alamofire (~> 5.5)
|
||||||
|
- "Capacitor (from `../../node_modules/@capacitor/ios`)"
|
||||||
|
- "CapacitorApp (from `../../node_modules/@capacitor/app`)"
|
||||||
|
- "CapacitorClipboard (from `../../node_modules/@capacitor/clipboard`)"
|
||||||
|
- "CapacitorCordova (from `../../node_modules/@capacitor/ios`)"
|
||||||
|
- "CapacitorDialog (from `../../node_modules/@capacitor/dialog`)"
|
||||||
|
- "CapacitorHaptics (from `../../node_modules/@capacitor/haptics`)"
|
||||||
|
- "CapacitorNetwork (from `../../node_modules/@capacitor/network`)"
|
||||||
|
- "CapacitorPreferences (from `../../node_modules/@capacitor/preferences`)"
|
||||||
|
- "CapacitorStatusBar (from `../../node_modules/@capacitor/status-bar`)"
|
||||||
|
- CordovaPlugins (from `../capacitor-cordova-ios-plugins`)
|
||||||
|
- RealmSwift (~> 10)
|
||||||
|
|
||||||
|
SPEC REPOS:
|
||||||
|
trunk:
|
||||||
|
- Alamofire
|
||||||
|
- Realm
|
||||||
|
- RealmSwift
|
||||||
|
|
||||||
|
EXTERNAL SOURCES:
|
||||||
|
Capacitor:
|
||||||
|
:path: "../../node_modules/@capacitor/ios"
|
||||||
|
CapacitorApp:
|
||||||
|
:path: "../../node_modules/@capacitor/app"
|
||||||
|
CapacitorClipboard:
|
||||||
|
:path: "../../node_modules/@capacitor/clipboard"
|
||||||
|
CapacitorCordova:
|
||||||
|
:path: "../../node_modules/@capacitor/ios"
|
||||||
|
CapacitorDialog:
|
||||||
|
:path: "../../node_modules/@capacitor/dialog"
|
||||||
|
CapacitorHaptics:
|
||||||
|
:path: "../../node_modules/@capacitor/haptics"
|
||||||
|
CapacitorNetwork:
|
||||||
|
:path: "../../node_modules/@capacitor/network"
|
||||||
|
CapacitorPreferences:
|
||||||
|
:path: "../../node_modules/@capacitor/preferences"
|
||||||
|
CapacitorStatusBar:
|
||||||
|
:path: "../../node_modules/@capacitor/status-bar"
|
||||||
|
CordovaPlugins:
|
||||||
|
:path: "../capacitor-cordova-ios-plugins"
|
||||||
|
|
||||||
|
SPEC CHECKSUMS:
|
||||||
|
Alamofire: 4e95d97098eacb88856099c4fc79b526a299e48c
|
||||||
|
Capacitor: 6002aadd64492438e5242325025045235dcb7e84
|
||||||
|
CapacitorApp: acd42fe8561fe751ad5b5f459aa85e6acd7bee24
|
||||||
|
CapacitorClipboard: 4c092a2608520afb799429e63820256c3a59f1e5
|
||||||
|
CapacitorCordova: c6249dcb2cf04dd835c0e99df1df4b9c8ad997e2
|
||||||
|
CapacitorDialog: c8a6558d29767e76a32a056bb5e0fc9104b985b0
|
||||||
|
CapacitorHaptics: 213b3a1f3efd6dbf6e6b76a1b2bb0399cf43b213
|
||||||
|
CapacitorNetwork: 7126b3d2d23ca60d5ac0d8d2ecccfab0b1f305c6
|
||||||
|
CapacitorPreferences: 1d66dc32299f55ed632c5611f312878979275ea5
|
||||||
|
CapacitorStatusBar: 65933e554bb5d65b361deaa936a93616086a2608
|
||||||
|
CordovaPlugins: b7ac282a1681fad663e14dcbe719249f738b88ce
|
||||||
|
Realm: 3fd136cb4c83a927482a7f1612496d37beed3cf5
|
||||||
|
RealmSwift: 513d4dcbf5bfc4d573454088b592685fc48dd716
|
||||||
|
|
||||||
|
PODFILE CHECKSUM: 05c80969578f3260e71d903c6ddb969847bcceb2
|
||||||
|
|
||||||
|
COCOAPODS: 1.12.1
|
||||||
+60
-20
@@ -3,10 +3,6 @@
|
|||||||
<app-appbar />
|
<app-appbar />
|
||||||
<div id="content" class="overflow-hidden relative" :class="isPlayerOpen ? 'playerOpen' : ''">
|
<div id="content" class="overflow-hidden relative" :class="isPlayerOpen ? 'playerOpen' : ''">
|
||||||
<Nuxt />
|
<Nuxt />
|
||||||
|
|
||||||
<div v-if="attemptingConnection" class="absolute top-0 left-0 z-50 w-full h-full flex items-center justify-center">
|
|
||||||
<ui-loading-indicator text="Connecting to server..." class="mt-9" />
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
<app-audio-player-container ref="streamContainer" />
|
<app-audio-player-container ref="streamContainer" />
|
||||||
<modals-libraries-modal />
|
<modals-libraries-modal />
|
||||||
@@ -19,12 +15,15 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
|
import { CapacitorHttp } from '@capacitor/core'
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
inittingLibraries: false,
|
inittingLibraries: false,
|
||||||
hasMounted: false,
|
hasMounted: false,
|
||||||
disconnectTime: 0
|
disconnectTime: 0,
|
||||||
|
timeLostFocus: 0
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
watch: {
|
watch: {
|
||||||
@@ -45,8 +44,12 @@ export default {
|
|||||||
if (timeSinceDisconnect > 5000) {
|
if (timeSinceDisconnect > 5000) {
|
||||||
console.log('Time since disconnect was', timeSinceDisconnect, 'sync with server')
|
console.log('Time since disconnect was', timeSinceDisconnect, 'sync with server')
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
// TODO: Some issue here
|
if (this.$platform === 'ios') {
|
||||||
this.syncLocalMediaProgress()
|
// TODO: Update ios to not use this
|
||||||
|
this.syncLocalMediaProgress()
|
||||||
|
} else {
|
||||||
|
this.syncLocalSessions()
|
||||||
|
}
|
||||||
}, 4000)
|
}, 4000)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -96,6 +99,21 @@ export default {
|
|||||||
|
|
||||||
await this.$store.dispatch('user/loadUserSettings')
|
await this.$store.dispatch('user/loadUserSettings')
|
||||||
},
|
},
|
||||||
|
async postRequest(url, data, headers, connectTimeout = 30000) {
|
||||||
|
const options = {
|
||||||
|
url,
|
||||||
|
headers,
|
||||||
|
data,
|
||||||
|
connectTimeout
|
||||||
|
}
|
||||||
|
const response = await CapacitorHttp.post(options)
|
||||||
|
console.log('[default] POST request response', response)
|
||||||
|
if (response.status >= 400) {
|
||||||
|
throw new Error(response.data)
|
||||||
|
} else {
|
||||||
|
return response.data
|
||||||
|
}
|
||||||
|
},
|
||||||
async attemptConnection() {
|
async attemptConnection() {
|
||||||
console.warn('[default] attemptConnection')
|
console.warn('[default] attemptConnection')
|
||||||
if (!this.networkConnected) {
|
if (!this.networkConnected) {
|
||||||
@@ -125,10 +143,8 @@ export default {
|
|||||||
|
|
||||||
console.log(`[default] Got server config, attempt authorize ${serverConfig.address}`)
|
console.log(`[default] Got server config, attempt authorize ${serverConfig.address}`)
|
||||||
|
|
||||||
const authRes = await this.$axios.$post(`${serverConfig.address}/api/authorize`, null, { headers: { Authorization: `Bearer ${serverConfig.token}` }, timeout: 3000 }).catch((error) => {
|
const authRes = await this.postRequest(`${serverConfig.address}/api/authorize`, null, { Authorization: `Bearer ${serverConfig.token}` }, 10000).catch((error) => {
|
||||||
console.error('[Server] Server auth failed', error)
|
console.error('[default] Server auth failed', error)
|
||||||
const errorMsg = error.response ? error.response.data || 'Unknown Error' : 'Unknown Error'
|
|
||||||
this.error = errorMsg
|
|
||||||
return false
|
return false
|
||||||
})
|
})
|
||||||
if (!authRes) {
|
if (!authRes) {
|
||||||
@@ -190,10 +206,12 @@ export default {
|
|||||||
|
|
||||||
console.log('[default] Calling syncLocalSessions')
|
console.log('[default] Calling syncLocalSessions')
|
||||||
const response = await this.$db.syncLocalSessionsWithServer()
|
const response = await this.$db.syncLocalSessionsWithServer()
|
||||||
if (response && response.error) {
|
if (response?.error) {
|
||||||
console.error('[default] Failed to sync local sessions', response.error)
|
console.error('[default] Failed to sync local sessions', response.error)
|
||||||
} else {
|
} else {
|
||||||
console.log('[default] Successfully synced local sessions')
|
console.log('[default] Successfully synced local sessions')
|
||||||
|
// Reload local media progresses
|
||||||
|
await this.$store.dispatch('globals/loadLocalMediaProgress')
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
async syncLocalMediaProgress() {
|
async syncLocalMediaProgress() {
|
||||||
@@ -227,7 +245,7 @@ export default {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
userUpdated(user) {
|
userUpdated(user) {
|
||||||
console.log('[default] userUpdated:', JSON.stringify(user))
|
// console.log('[default] userUpdated:', JSON.stringify(user))
|
||||||
if (this.user && this.user.id == user.id) {
|
if (this.user && this.user.id == user.id) {
|
||||||
this.$store.commit('user/setUser', user)
|
this.$store.commit('user/setUser', user)
|
||||||
}
|
}
|
||||||
@@ -236,13 +254,6 @@ export default {
|
|||||||
const prog = payload.data // MediaProgress
|
const prog = payload.data // MediaProgress
|
||||||
console.log(`[default] userMediaProgressUpdate checking for local media progress ${payload.id}`)
|
console.log(`[default] userMediaProgressUpdate checking for local media progress ${payload.id}`)
|
||||||
|
|
||||||
// Check if this media item is currently open in the player, paused, and this progress update is coming from a different session
|
|
||||||
const isMediaOpenInPlayer = this.$store.getters['getIsMediaStreaming'](prog.libraryItemId, prog.episodeId)
|
|
||||||
if (isMediaOpenInPlayer && this.$store.getters['getCurrentPlaybackSessionId'] !== payload.sessionId && !this.$store.state.playerIsPlaying) {
|
|
||||||
console.log('[default] userMediaProgressUpdated for current open media item', payload.data.currentTime)
|
|
||||||
this.$eventBus.$emit('playback-time-update', payload.data.currentTime)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Update local media progress if exists
|
// Update local media progress if exists
|
||||||
const localProg = await this.$db.getLocalMediaProgressForServerItem({ libraryItemId: prog.libraryItemId, episodeId: prog.episodeId })
|
const localProg = await this.$db.getLocalMediaProgressForServerItem({ libraryItemId: prog.libraryItemId, episodeId: prog.episodeId })
|
||||||
|
|
||||||
@@ -255,6 +266,13 @@ export default {
|
|||||||
console.log(`[default] syncing progress server lastUpdate > local lastUpdate. server currentTime=${prog.currentTime} local currentTime=${localProg.currentTime} | server/local isFinished=${prog.isFinished}/${localProg.isFinished}`)
|
console.log(`[default] syncing progress server lastUpdate > local lastUpdate. server currentTime=${prog.currentTime} local currentTime=${localProg.currentTime} | server/local isFinished=${prog.isFinished}/${localProg.isFinished}`)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Check if this media item is currently open in the player, paused, and this progress update is coming from a different session
|
||||||
|
const isMediaOpenInPlayer = this.$store.getters['getIsMediaStreaming'](prog.libraryItemId, prog.episodeId)
|
||||||
|
if (isMediaOpenInPlayer && this.$store.getters['getCurrentPlaybackSessionId'] !== payload.sessionId && !this.$store.state.playerIsPlaying) {
|
||||||
|
console.log('[default] userMediaProgressUpdated for current open media item', payload.data.currentTime)
|
||||||
|
this.$eventBus.$emit('playback-time-update', payload.data.currentTime)
|
||||||
|
}
|
||||||
|
|
||||||
// Server progress is more up-to-date
|
// Server progress is more up-to-date
|
||||||
console.log(`[default] syncing progress from server with local item for "${prog.libraryItemId}" ${prog.episodeId ? `episode ${prog.episodeId}` : ''} | server lastUpdate=${prog.lastUpdate} > local lastUpdate=${localProg.lastUpdate}`)
|
console.log(`[default] syncing progress from server with local item for "${prog.libraryItemId}" ${prog.episodeId ? `episode ${prog.episodeId}` : ''} | server lastUpdate=${prog.lastUpdate} > local lastUpdate=${localProg.lastUpdate}`)
|
||||||
const payload = {
|
const payload = {
|
||||||
@@ -297,9 +315,30 @@ export default {
|
|||||||
console.log(`[default] local media progress updated for ${newLocalMediaProgress.id}`)
|
console.log(`[default] local media progress updated for ${newLocalMediaProgress.id}`)
|
||||||
this.$store.commit('globals/updateLocalMediaProgress', newLocalMediaProgress)
|
this.$store.commit('globals/updateLocalMediaProgress', newLocalMediaProgress)
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
async visibilityChanged() {
|
||||||
|
if (document.visibilityState === 'visible') {
|
||||||
|
const elapsedTimeOutOfFocus = Date.now() - this.timeLostFocus
|
||||||
|
console.log(`✅ [default] device visibility: has focus (${elapsedTimeOutOfFocus}ms out of focus)`)
|
||||||
|
// If device out of focus for more than 30s then reload local media progress
|
||||||
|
if (elapsedTimeOutOfFocus > 30000) {
|
||||||
|
console.log(`✅ [default] device visibility: reloading local media progress`)
|
||||||
|
// Reload local media progresses
|
||||||
|
await this.$store.dispatch('globals/loadLocalMediaProgress')
|
||||||
|
}
|
||||||
|
if (document.visibilityState === 'visible') {
|
||||||
|
this.$eventBus.$emit('device-focus-update', true)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
console.log('⛔️ [default] device visibility: does NOT have focus')
|
||||||
|
this.timeLostFocus = Date.now()
|
||||||
|
this.$eventBus.$emit('device-focus-update', false)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
async mounted() {
|
async mounted() {
|
||||||
|
document.addEventListener('visibilitychange', this.visibilityChanged)
|
||||||
|
|
||||||
this.$socket.on('user_updated', this.userUpdated)
|
this.$socket.on('user_updated', this.userUpdated)
|
||||||
this.$socket.on('user_media_progress_updated', this.userMediaProgressUpdated)
|
this.$socket.on('user_media_progress_updated', this.userMediaProgressUpdated)
|
||||||
|
|
||||||
@@ -337,6 +376,7 @@ export default {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
beforeDestroy() {
|
beforeDestroy() {
|
||||||
|
document.removeEventListener('visibilitychange', this.visibilityChanged)
|
||||||
this.$socket.off('user_updated', this.userUpdated)
|
this.$socket.off('user_updated', this.userUpdated)
|
||||||
this.$socket.off('user_media_progress_updated', this.userMediaProgressUpdated)
|
this.$socket.off('user_media_progress_updated', this.userMediaProgressUpdated)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -44,6 +44,7 @@ export default {
|
|||||||
'@/plugins/localStore.js',
|
'@/plugins/localStore.js',
|
||||||
'@/plugins/init.client.js',
|
'@/plugins/init.client.js',
|
||||||
'@/plugins/axios.js',
|
'@/plugins/axios.js',
|
||||||
|
'@/plugins/nativeHttp.js',
|
||||||
'@/plugins/capacitor/index.js',
|
'@/plugins/capacitor/index.js',
|
||||||
'@/plugins/capacitor/AbsAudioPlayer.js',
|
'@/plugins/capacitor/AbsAudioPlayer.js',
|
||||||
'@/plugins/toast.js',
|
'@/plugins/toast.js',
|
||||||
|
|||||||
Generated
+3388
-2665
File diff suppressed because it is too large
Load Diff
+12
-12
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "audiobookshelf-app",
|
"name": "audiobookshelf-app",
|
||||||
"version": "0.9.65-beta",
|
"version": "0.9.66-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",
|
||||||
@@ -13,16 +13,16 @@
|
|||||||
"ionic:serve": "npm run start"
|
"ionic:serve": "npm run start"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@capacitor/android": "^4.0.0",
|
"@capacitor/android": "^5.0.0",
|
||||||
"@capacitor/app": "^4.0.0",
|
"@capacitor/app": "^5.0.0",
|
||||||
"@capacitor/clipboard": "^4.1.0",
|
"@capacitor/clipboard": "^5.0.0",
|
||||||
"@capacitor/core": "^4.0.0",
|
"@capacitor/core": "^5.0.0",
|
||||||
"@capacitor/dialog": "^4.0.0",
|
"@capacitor/dialog": "^5.0.0",
|
||||||
"@capacitor/haptics": "^4.0.0",
|
"@capacitor/haptics": "^5.0.0",
|
||||||
"@capacitor/ios": "^4.0.0",
|
"@capacitor/ios": "^5.0.0",
|
||||||
"@capacitor/network": "^4.0.0",
|
"@capacitor/network": "^5.0.0",
|
||||||
"@capacitor/preferences": "^4.0.2",
|
"@capacitor/preferences": "^5.0.0",
|
||||||
"@capacitor/status-bar": "^4.0.0",
|
"@capacitor/status-bar": "^5.0.0",
|
||||||
"@nuxtjs/axios": "^5.13.6",
|
"@nuxtjs/axios": "^5.13.6",
|
||||||
"@teckel/vue-pdf": "^4.3.5",
|
"@teckel/vue-pdf": "^4.3.5",
|
||||||
"buffer": "^6.0.3",
|
"buffer": "^6.0.3",
|
||||||
@@ -41,7 +41,7 @@
|
|||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@babel/core": "7.13.15",
|
"@babel/core": "7.13.15",
|
||||||
"@babel/preset-env": "7.13.15",
|
"@babel/preset-env": "7.13.15",
|
||||||
"@capacitor/cli": "^4.0.0",
|
"@capacitor/cli": "^5.0.0",
|
||||||
"@nuxtjs/tailwindcss": "^4.2.1",
|
"@nuxtjs/tailwindcss": "^4.2.1",
|
||||||
"postcss": "^8.3.5",
|
"postcss": "^8.3.5",
|
||||||
"tailwindcss": "^3.3.2"
|
"tailwindcss": "^3.3.2"
|
||||||
|
|||||||
+3
-2
@@ -4,7 +4,7 @@
|
|||||||
|
|
||||||
<ui-text-input-with-label :value="username" label="Username" disabled class="my-2" />
|
<ui-text-input-with-label :value="username" label="Username" disabled class="my-2" />
|
||||||
|
|
||||||
<ui-btn color="primary flex items-center justify-between gap-2 ml-auto text-base mt-8" @click="logout">Logout<span class="material-icons" style="font-size: 1.1rem">logout</span></ui-btn>
|
<ui-btn color="primary flex items-center justify-between gap-2 ml-auto text-base mt-8" @click="logout">Switch Server/User<span class="material-icons" style="font-size: 1.1rem">logout</span></ui-btn>
|
||||||
|
|
||||||
<div class="flex justify-end items-center m-4 gap-3 right-0 bottom-0 absolute">
|
<div class="flex justify-end items-center m-4 gap-3 right-0 bottom-0 absolute">
|
||||||
<p class="text-smtext-yellow-400 text-right">Report bugs, request features, provide feedback, and contribute on <a class="underline" href="https://github.com/advplyr/audiobookshelf-app" target="_blank">GitHub</a></p>
|
<p class="text-smtext-yellow-400 text-right">Report bugs, request features, provide feedback, and contribute on <a class="underline" href="https://github.com/advplyr/audiobookshelf-app" target="_blank">GitHub</a></p>
|
||||||
@@ -49,7 +49,7 @@ export default {
|
|||||||
async logout() {
|
async logout() {
|
||||||
await this.$hapticsImpact()
|
await this.$hapticsImpact()
|
||||||
if (this.user) {
|
if (this.user) {
|
||||||
await this.$axios.$post('/logout').catch((error) => {
|
await this.$nativeHttp.post('/logout').catch((error) => {
|
||||||
console.error(error)
|
console.error(error)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@@ -58,6 +58,7 @@ export default {
|
|||||||
await this.$db.logout()
|
await this.$db.logout()
|
||||||
this.$localStore.removeLastLibraryId()
|
this.$localStore.removeLastLibraryId()
|
||||||
this.$store.commit('user/logout')
|
this.$store.commit('user/logout')
|
||||||
|
this.$store.commit('libraries/setCurrentLibrary', null)
|
||||||
this.$router.push('/connect')
|
this.$router.push('/connect')
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -26,9 +26,12 @@ export default {
|
|||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
async init() {
|
async init() {
|
||||||
|
if (!this.currentLibraryId) {
|
||||||
|
return
|
||||||
|
}
|
||||||
this.loadedLibraryId = this.currentLibraryId
|
this.loadedLibraryId = this.currentLibraryId
|
||||||
this.authors = await this.$axios
|
this.authors = await this.$nativeHttp
|
||||||
.$get(`/api/libraries/${this.currentLibraryId}/authors`)
|
.get(`/api/libraries/${this.currentLibraryId}/authors`)
|
||||||
.then((response) => response.authors)
|
.then((response) => response.authors)
|
||||||
.catch((error) => {
|
.catch((error) => {
|
||||||
console.error('Failed to load authors', error)
|
console.error('Failed to load authors', error)
|
||||||
|
|||||||
@@ -1,5 +1,9 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="w-full h-full min-h-full relative">
|
<div class="w-full h-full min-h-full relative">
|
||||||
|
<div v-if="attemptingConnection" class="w-full pt-4 flex items-center justify-center">
|
||||||
|
<widgets-loading-spinner />
|
||||||
|
<p class="pl-4">Attempting server connection...</p>
|
||||||
|
</div>
|
||||||
<div v-if="shelves.length && isLoading" class="w-full pt-4 flex items-center justify-center">
|
<div v-if="shelves.length && isLoading" class="w-full pt-4 flex items-center justify-center">
|
||||||
<widgets-loading-spinner />
|
<widgets-loading-spinner />
|
||||||
<p class="pl-4">Loading server data...</p>
|
<p class="pl-4">Loading server data...</p>
|
||||||
@@ -232,7 +236,7 @@ export default {
|
|||||||
console.log('[categories] Local shelves set', this.shelves.length, this.lastLocalFetch)
|
console.log('[categories] Local shelves set', this.shelves.length, this.lastLocalFetch)
|
||||||
|
|
||||||
if (isConnectedToServerWithInternet) {
|
if (isConnectedToServerWithInternet) {
|
||||||
const categories = await this.$axios.$get(`/api/libraries/${this.currentLibraryId}/personalized?minified=1&include=rssfeed`).catch((error) => {
|
const categories = await this.$nativeHttp.get(`/api/libraries/${this.currentLibraryId}/personalized?minified=1&include=rssfeed,numEpisodesIncomplete`).catch((error) => {
|
||||||
console.error('[categories] Failed to fetch categories', error)
|
console.error('[categories] Failed to fetch categories', error)
|
||||||
return []
|
return []
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -29,7 +29,7 @@ export default {
|
|||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
async addEpisodeToPlaylist(episode) {
|
async addEpisodeToPlaylist(episode) {
|
||||||
const libraryItem = await this.$axios.$get(`/api/items/${episode.libraryItemId}`).catch((error) => {
|
const libraryItem = await this.$nativeHttp.get(`/api/items/${episode.libraryItemId}`).catch((error) => {
|
||||||
console.error('Failed to get library item', error)
|
console.error('Failed to get library item', error)
|
||||||
this.$toast.error('Failed to get library item')
|
this.$toast.error('Failed to get library item')
|
||||||
return null
|
return null
|
||||||
@@ -42,7 +42,7 @@ export default {
|
|||||||
async loadRecentEpisodes(page = 0) {
|
async loadRecentEpisodes(page = 0) {
|
||||||
this.loadedLibraryId = this.currentLibraryId
|
this.loadedLibraryId = this.currentLibraryId
|
||||||
this.processing = true
|
this.processing = true
|
||||||
const episodePayload = await this.$axios.$get(`/api/libraries/${this.currentLibraryId}/recent-episodes?limit=25&page=${page}`).catch((error) => {
|
const episodePayload = await this.$nativeHttp.get(`/api/libraries/${this.currentLibraryId}/recent-episodes?limit=25&page=${page}`).catch((error) => {
|
||||||
console.error('Failed to get recent episodes', error)
|
console.error('Failed to get recent episodes', error)
|
||||||
this.$toast.error('Failed to get recent episodes')
|
this.$toast.error('Failed to get recent episodes')
|
||||||
return null
|
return null
|
||||||
|
|||||||
@@ -88,7 +88,7 @@ export default {
|
|||||||
},
|
},
|
||||||
async checkRSSFeed(rssFeed) {
|
async checkRSSFeed(rssFeed) {
|
||||||
this.processing = true
|
this.processing = true
|
||||||
var payload = await this.$axios.$post(`/api/podcasts/feed`, { rssFeed }).catch((error) => {
|
var payload = await this.$nativeHttp.post(`/api/podcasts/feed`, { rssFeed }).catch((error) => {
|
||||||
console.error('Failed to get feed', error)
|
console.error('Failed to get feed', error)
|
||||||
this.$toast.error('Failed to get podcast feed')
|
this.$toast.error('Failed to get podcast feed')
|
||||||
return null
|
return null
|
||||||
@@ -103,7 +103,7 @@ export default {
|
|||||||
async submitSearch(term) {
|
async submitSearch(term) {
|
||||||
this.processing = true
|
this.processing = true
|
||||||
this.termSearched = ''
|
this.termSearched = ''
|
||||||
const results = await this.$axios.$get(`/api/search/podcast?term=${encodeURIComponent(term)}`).catch((error) => {
|
const results = await this.$nativeHttp.get(`/api/search/podcast?term=${encodeURIComponent(term)}`).catch((error) => {
|
||||||
console.error('Search request failed', error)
|
console.error('Search request failed', error)
|
||||||
return []
|
return []
|
||||||
})
|
})
|
||||||
@@ -119,7 +119,7 @@ export default {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
this.processing = true
|
this.processing = true
|
||||||
const payload = await this.$axios.$post(`/api/podcasts/feed`, { rssFeed: podcast.feedUrl }).catch((error) => {
|
const payload = await this.$nativeHttp.post(`/api/podcasts/feed`, { rssFeed: podcast.feedUrl }).catch((error) => {
|
||||||
console.error('Failed to get feed', error)
|
console.error('Failed to get feed', error)
|
||||||
this.$toast.error('Failed to get podcast feed')
|
this.$toast.error('Failed to get podcast feed')
|
||||||
return null
|
return null
|
||||||
|
|||||||
@@ -465,8 +465,8 @@ export default {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (serverItemProgressId) {
|
if (serverItemProgressId) {
|
||||||
await this.$axios
|
await this.$nativeHttp
|
||||||
.$delete(`/api/me/progress/${serverItemProgressId}`)
|
.delete(`/api/me/progress/${serverItemProgressId}`)
|
||||||
.then(() => {
|
.then(() => {
|
||||||
console.log('Progress reset complete')
|
console.log('Progress reset complete')
|
||||||
this.$toast.success(`Your progress was reset`)
|
this.$toast.success(`Your progress was reset`)
|
||||||
@@ -503,7 +503,7 @@ export default {
|
|||||||
const updatePayload = {
|
const updatePayload = {
|
||||||
isFinished: !this.userIsFinished
|
isFinished: !this.userIsFinished
|
||||||
}
|
}
|
||||||
this.$axios.$patch(`/api/me/progress/${this.libraryItemId}/${this.episode.id}`, updatePayload).catch((error) => {
|
this.$nativeHttp.patch(`/api/me/progress/${this.libraryItemId}/${this.episode.id}`, updatePayload).catch((error) => {
|
||||||
console.error('Failed', error)
|
console.error('Failed', error)
|
||||||
this.$toast.error(`Failed to mark as ${updatePayload.isFinished ? 'Finished' : 'Not Finished'}`)
|
this.$toast.error(`Failed to mark as ${updatePayload.isFinished ? 'Finished' : 'Not Finished'}`)
|
||||||
})
|
})
|
||||||
@@ -519,8 +519,8 @@ export default {
|
|||||||
|
|
||||||
if (value) {
|
if (value) {
|
||||||
this.processing = true
|
this.processing = true
|
||||||
this.$axios
|
this.$nativeHttp
|
||||||
.$delete(`/api/podcasts/${this.serverLibraryItemId}/episode/${this.serverEpisodeId}?hard=1`)
|
.delete(`/api/podcasts/${this.serverLibraryItemId}/episode/${this.serverEpisodeId}?hard=1`)
|
||||||
.then(() => {
|
.then(() => {
|
||||||
this.$toast.success('Episode deleted from server')
|
this.$toast.success('Episode deleted from server')
|
||||||
this.$router.replace(`/item/${this.serverLibraryItemId}`)
|
this.$router.replace(`/item/${this.serverLibraryItemId}`)
|
||||||
|
|||||||
+23
-13
@@ -68,8 +68,8 @@
|
|||||||
<div v-if="podcastAuthor" class="text-sm">{{ podcastAuthor }}</div>
|
<div v-if="podcastAuthor" class="text-sm">{{ podcastAuthor }}</div>
|
||||||
<div v-else-if="bookAuthors && bookAuthors.length" class="text-sm">
|
<div v-else-if="bookAuthors && bookAuthors.length" class="text-sm">
|
||||||
<template v-for="(author, index) in bookAuthors">
|
<template v-for="(author, index) in bookAuthors">
|
||||||
<nuxt-link :key="author.id" :to="`/bookshelf/library?filter=authors.${$encode(author.id)}`" class="underline">{{ author.name }}</nuxt-link>
|
<nuxt-link :key="author.id" :to="`/bookshelf/library?filter=authors.${$encode(author.id)}`" class="underline">{{ author.name }}</nuxt-link
|
||||||
<span :key="`${author.id}-comma`" v-if="index < bookAuthors.length - 1">,</span>
|
><span :key="`${author.id}-comma`" v-if="index < bookAuthors.length - 1">, </span>
|
||||||
</template>
|
</template>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -79,8 +79,8 @@
|
|||||||
<div v-if="series && series.length" class="text-white text-opacity-60 uppercase text-sm">Series</div>
|
<div v-if="series && series.length" class="text-white text-opacity-60 uppercase text-sm">Series</div>
|
||||||
<div v-if="series && series.length" class="truncate text-sm">
|
<div v-if="series && series.length" class="truncate text-sm">
|
||||||
<template v-for="(series, index) in seriesList">
|
<template v-for="(series, index) in seriesList">
|
||||||
<nuxt-link :key="series.id" :to="`/bookshelf/series/${series.id}`" class="underline">{{ series.text }}</nuxt-link>
|
<nuxt-link :key="series.id" :to="`/bookshelf/series/${series.id}`" class="underline">{{ series.text }}</nuxt-link
|
||||||
<span :key="`${series.id}-comma`" v-if="index < seriesList.length - 1">, </span>
|
><span :key="`${series.id}-comma`" v-if="index < seriesList.length - 1">, </span>
|
||||||
</template>
|
</template>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -90,16 +90,16 @@
|
|||||||
<div v-if="narrators && narrators.length" class="text-white text-opacity-60 uppercase text-sm">{{ narrators.length === 1 ? 'Narrator' : 'Narrators' }}</div>
|
<div v-if="narrators && narrators.length" class="text-white text-opacity-60 uppercase text-sm">{{ narrators.length === 1 ? 'Narrator' : 'Narrators' }}</div>
|
||||||
<div v-if="narrators && narrators.length" class="truncate text-sm">
|
<div v-if="narrators && narrators.length" class="truncate text-sm">
|
||||||
<template v-for="(narrator, index) in narrators">
|
<template v-for="(narrator, index) in narrators">
|
||||||
<nuxt-link :key="narrator" :to="`/bookshelf/library?filter=narrators.${$encode(narrator)}`" class="underline">{{ narrator }}</nuxt-link>
|
<nuxt-link :key="narrator" :to="`/bookshelf/library?filter=narrators.${$encode(narrator)}`" class="underline">{{ narrator }}</nuxt-link
|
||||||
<span :key="index" v-if="index < narrators.length - 1">,</span>
|
><span :key="index" v-if="index < narrators.length - 1">, </span>
|
||||||
</template>
|
</template>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div v-if="genres.length" class="text-white text-opacity-60 uppercase text-sm">{{ genres.length === 1 ? 'Genre' : 'Genres' }}</div>
|
<div v-if="genres.length" class="text-white text-opacity-60 uppercase text-sm">{{ genres.length === 1 ? 'Genre' : 'Genres' }}</div>
|
||||||
<div v-if="genres.length" class="truncate text-sm">
|
<div v-if="genres.length" class="truncate text-sm">
|
||||||
<template v-for="(genre, index) in genres">
|
<template v-for="(genre, index) in genres">
|
||||||
<nuxt-link :key="genre" :to="`/bookshelf/library?filter=genres.${$encode(genre)}`" class="underline">{{ genre }}</nuxt-link>
|
<nuxt-link :key="genre" :to="`/bookshelf/library?filter=genres.${$encode(genre)}`" class="underline">{{ genre }}</nuxt-link
|
||||||
<span :key="index" v-if="index < genres.length - 1">,</span>
|
><span :key="index" v-if="index < genres.length - 1">, </span>
|
||||||
</template>
|
</template>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -602,7 +602,7 @@ export default {
|
|||||||
} else if (this.isLocal) {
|
} else if (this.isLocal) {
|
||||||
localEpisode = episode
|
localEpisode = episode
|
||||||
}
|
}
|
||||||
const serverEpisodeId = !this.isLocal ? episodeId : localEpisode ? localEpisode.serverEpisodeId : null
|
const serverEpisodeId = !this.isLocal ? episodeId : localEpisode?.serverEpisodeId || null
|
||||||
|
|
||||||
if (serverEpisodeId && this.serverLibraryItemId && this.isCasting) {
|
if (serverEpisodeId && this.serverLibraryItemId && this.isCasting) {
|
||||||
// If casting and connected to server for local library item then send server library item id
|
// If casting and connected to server for local library item then send server library item id
|
||||||
@@ -651,8 +651,8 @@ export default {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (this.serverLibraryItemId && serverMediaProgressId) {
|
if (this.serverLibraryItemId && serverMediaProgressId) {
|
||||||
await this.$axios
|
await this.$nativeHttp
|
||||||
.$delete(`/api/me/progress/${serverMediaProgressId}`)
|
.delete(`/api/me/progress/${serverMediaProgressId}`)
|
||||||
.then(() => {
|
.then(() => {
|
||||||
console.log('Progress reset complete')
|
console.log('Progress reset complete')
|
||||||
this.$toast.success(`Your progress was reset`)
|
this.$toast.success(`Your progress was reset`)
|
||||||
@@ -798,8 +798,8 @@ export default {
|
|||||||
const updatePayload = {
|
const updatePayload = {
|
||||||
isFinished: !this.userIsFinished
|
isFinished: !this.userIsFinished
|
||||||
}
|
}
|
||||||
this.$axios
|
this.$nativeHttp
|
||||||
.$patch(`/api/me/progress/${this.libraryItemId}`, updatePayload)
|
.patch(`/api/me/progress/${this.libraryItemId}`, updatePayload)
|
||||||
.catch((error) => {
|
.catch((error) => {
|
||||||
console.error('Failed', error)
|
console.error('Failed', error)
|
||||||
this.$toast.error(`Failed to mark as ${updatePayload.isFinished ? 'Finished' : 'Not Finished'}`)
|
this.$toast.error(`Failed to mark as ${updatePayload.isFinished ? 'Finished' : 'Not Finished'}`)
|
||||||
@@ -837,9 +837,19 @@ export default {
|
|||||||
console.log('RSS Feed Closed', data)
|
console.log('RSS Feed Closed', data)
|
||||||
this.rssFeed = null
|
this.rssFeed = null
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
async setLibrary() {
|
||||||
|
if (!this.libraryItem.libraryId) return
|
||||||
|
await this.$store.dispatch('libraries/fetch', this.libraryItem.libraryId)
|
||||||
|
this.$localStore.setLastLibraryId(this.libraryItem.libraryId)
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
mounted() {
|
mounted() {
|
||||||
|
// If library of this item is different from current library then switch libraries
|
||||||
|
if (this.$store.state.libraries.currentLibraryId !== this.libraryItem.libraryId) {
|
||||||
|
this.setLibrary()
|
||||||
|
}
|
||||||
|
|
||||||
this.windowWidth = window.innerWidth
|
this.windowWidth = window.innerWidth
|
||||||
window.addEventListener('resize', this.windowResized)
|
window.addEventListener('resize', this.windowResized)
|
||||||
this.$eventBus.$on('library-changed', this.libraryChanged)
|
this.$eventBus.$on('library-changed', this.libraryChanged)
|
||||||
|
|||||||
+1
-1
@@ -100,7 +100,7 @@ export default {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
this.isFetching = true
|
this.isFetching = true
|
||||||
const results = await this.$axios.$get(`/api/libraries/${this.currentLibraryId}/search?q=${value}&limit=5`).catch((error) => {
|
const results = await this.$nativeHttp.get(`/api/libraries/${this.currentLibraryId}/search?q=${value}&limit=5`).catch((error) => {
|
||||||
console.error('Search error', error)
|
console.error('Search error', error)
|
||||||
return null
|
return null
|
||||||
})
|
})
|
||||||
|
|||||||
+4
-3
@@ -8,7 +8,8 @@
|
|||||||
</div>
|
</div>
|
||||||
<p class="pl-4">Use bookshelf view</p>
|
<p class="pl-4">Use bookshelf view</p>
|
||||||
</div>
|
</div>
|
||||||
<div class="flex items-center py-3" @click.stop="toggleLockOrientation">
|
<!-- screen.orientation.lock not supported on iOS webview -->
|
||||||
|
<div v-if="!isiOS" class="flex items-center py-3" @click.stop="toggleLockOrientation">
|
||||||
<div class="w-10 flex justify-center pointer-events-none">
|
<div class="w-10 flex justify-center pointer-events-none">
|
||||||
<ui-toggle-switch v-model="lockCurrentOrientation" />
|
<ui-toggle-switch v-model="lockCurrentOrientation" />
|
||||||
</div>
|
</div>
|
||||||
@@ -135,7 +136,7 @@ export default {
|
|||||||
moreMenuSetting: '',
|
moreMenuSetting: '',
|
||||||
settings: {
|
settings: {
|
||||||
disableAutoRewind: false,
|
disableAutoRewind: false,
|
||||||
enableAltView: false,
|
enableAltView: true,
|
||||||
jumpForwardTime: 10,
|
jumpForwardTime: 10,
|
||||||
jumpBackwardsTime: 10,
|
jumpBackwardsTime: 10,
|
||||||
enableMp3IndexSeeking: false,
|
enableMp3IndexSeeking: false,
|
||||||
@@ -414,7 +415,7 @@ export default {
|
|||||||
this.settings.enableAltView = !!deviceSettings.enableAltView
|
this.settings.enableAltView = !!deviceSettings.enableAltView
|
||||||
this.settings.jumpForwardTime = deviceSettings.jumpForwardTime || 10
|
this.settings.jumpForwardTime = deviceSettings.jumpForwardTime || 10
|
||||||
this.settings.jumpBackwardsTime = deviceSettings.jumpBackwardsTime || 10
|
this.settings.jumpBackwardsTime = deviceSettings.jumpBackwardsTime || 10
|
||||||
this.settings.toggleEnableMp3IndexSeeking = !!deviceSettings.toggleEnableMp3IndexSeeking
|
this.settings.enableMp3IndexSeeking = !!deviceSettings.enableMp3IndexSeeking
|
||||||
|
|
||||||
this.settings.lockOrientation = deviceSettings.lockOrientation || 'NONE'
|
this.settings.lockOrientation = deviceSettings.lockOrientation || 'NONE'
|
||||||
this.lockCurrentOrientation = this.settings.lockOrientation !== 'NONE'
|
this.lockCurrentOrientation = this.settings.lockOrientation !== 'NONE'
|
||||||
|
|||||||
+1
-1
@@ -99,7 +99,7 @@ export default {
|
|||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
async init() {
|
async init() {
|
||||||
this.listeningStats = await this.$axios.$get(`/api/me/listening-stats`).catch((err) => {
|
this.listeningStats = await this.$nativeHttp.get(`/api/me/listening-stats`).catch((err) => {
|
||||||
console.error('Failed to load listening sesions', err)
|
console.error('Failed to load listening sesions', err)
|
||||||
return []
|
return []
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -0,0 +1,46 @@
|
|||||||
|
import { CapacitorHttp } from '@capacitor/core'
|
||||||
|
|
||||||
|
export default function ({ store }, inject) {
|
||||||
|
const nativeHttp = {
|
||||||
|
request(method, _url, data, options = {}) {
|
||||||
|
let url = _url
|
||||||
|
const headers = {}
|
||||||
|
if (!url.startsWith('http') && !url.startsWith('capacitor')) {
|
||||||
|
const bearerToken = store.getters['user/getToken']
|
||||||
|
if (bearerToken) {
|
||||||
|
headers['Authorization'] = `Bearer ${bearerToken}`
|
||||||
|
} else {
|
||||||
|
console.warn('[nativeHttp] No Bearer Token for request')
|
||||||
|
}
|
||||||
|
const serverUrl = store.getters['user/getServerAddress']
|
||||||
|
if (serverUrl) {
|
||||||
|
url = `${serverUrl}${url}`
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (data) {
|
||||||
|
headers['Content-Type'] = 'application/json'
|
||||||
|
}
|
||||||
|
console.log(`[nativeHttp] Making ${method} request to ${url}`)
|
||||||
|
return CapacitorHttp.request({
|
||||||
|
method,
|
||||||
|
url,
|
||||||
|
data,
|
||||||
|
headers,
|
||||||
|
...options
|
||||||
|
}).then(res => res.data)
|
||||||
|
},
|
||||||
|
get(url, options = {}) {
|
||||||
|
return this.request('GET', url, undefined, options)
|
||||||
|
},
|
||||||
|
post(url, data, options = {}) {
|
||||||
|
return this.request('POST', url, data, options)
|
||||||
|
},
|
||||||
|
patch(url, data, options = {}) {
|
||||||
|
return this.request('PATCH', url, data, options)
|
||||||
|
},
|
||||||
|
delete(url, options = {}) {
|
||||||
|
return this.request('DELETE', url, undefined, options)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
inject('nativeHttp', nativeHttp)
|
||||||
|
}
|
||||||
@@ -11,14 +11,11 @@ Available to beta testers through Test Flight
|
|||||||
|
|
||||||
Join the beta testers and install the iOS app: https://testflight.apple.com/join/wiic7QIW
|
Join the beta testers and install the iOS app: https://testflight.apple.com/join/wiic7QIW
|
||||||
|
|
||||||
|
|
||||||
Join the discussion: https://github.com/advplyr/audiobookshelf-app/discussions/60
|
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
[Go to the main project repo github.com/advplyr/audiobookshelf](https://github.com/advplyr/audiobookshelf) or the project site [audiobookshelf.org](https://audiobookshelf.org)
|
[Go to the main project repo github.com/advplyr/audiobookshelf](https://github.com/advplyr/audiobookshelf) or the project site [audiobookshelf.org](https://audiobookshelf.org)
|
||||||
|
|
||||||
Join us on [discord](https://discord.gg/pJsjuNCKRq)
|
Join us on [discord](https://discord.gg/pJsjuNCKRq) or [Matrix](https://matrix.to/#/#audiobookshelf:matrix.org)
|
||||||
|
|
||||||
**Requires an Audiobookshelf server to connect with**
|
**Requires an Audiobookshelf server to connect with**
|
||||||
|
|
||||||
|
|||||||
+4
-4
@@ -39,8 +39,8 @@ export const actions = {
|
|||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
return this.$axios
|
return this.$nativeHttp
|
||||||
.$get(`/api/libraries/${libraryId}?include=filterdata`)
|
.get(`/api/libraries/${libraryId}?include=filterdata`)
|
||||||
.then((data) => {
|
.then((data) => {
|
||||||
const library = data.library
|
const library = data.library
|
||||||
const filterData = data.filterdata
|
const filterData = data.filterdata
|
||||||
@@ -75,8 +75,8 @@ export const actions = {
|
|||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
return this.$axios
|
return this.$nativeHttp
|
||||||
.$get(`/api/libraries`)
|
.get(`/api/libraries`)
|
||||||
.then((data) => {
|
.then((data) => {
|
||||||
// TODO: Server release 2.2.9 changed response to an object. Remove after a few releases
|
// TODO: Server release 2.2.9 changed response to an object. Remove after a few releases
|
||||||
const libraries = data.libraries || data
|
const libraries = data.libraries || data
|
||||||
|
|||||||
Reference in New Issue
Block a user