Compare commits

...
Author SHA1 Message Date
advplyr 6bbcd300e4 iOS version bump 0.9.59 2022-11-05 16:41:45 -05:00
advplyr c54d9b3c5f Version bump 0.9.59 2022-11-05 16:29:30 -05:00
advplyr 9ab03fbcd4 fix & update comic reader for comics with subfolders and better sorting 2022-11-05 15:13:52 -05:00
advplyr a852114e9c Update:Use cover aspect ratio from library setting 2022-10-22 08:59:10 -05:00
advplyr 3c9b8c1c6f Fix read btn color 2022-10-21 16:29:46 -05:00
advplyr 4735b51dc0 Update:New library icons 2022-10-19 10:56:31 -05:00
advplyrandGitHub 23927a1d59 Merge pull request #413 from wnhrt/master
Colored status and navigationbar
2022-10-16 12:08:55 -05:00
Niklas Weinhart 40b592f7c1 Colored status and navigationbar 2022-10-16 05:37:35 +02:00
advplyrandGitHub 4da31f5850 Merge pull request #391 from ronaldheft/fix-ios-pause-during-seek
fix: Player sometimes pauses during seek on iOS
2022-09-23 15:37:50 -05:00
advplyrandGitHub f79e96cbfe Merge pull request #397 from ronaldheft/ios-player-seek-back-time-fixes
feat: Groundwork for testable iOS swift code
2022-09-23 15:36:59 -05:00
ronaldheft 2dbce1e3bb fix: Negative currentTime after seek back 2022-09-21 20:16:29 -04:00
ronaldheft cdfbbcbf8a refactor: Use refactored seek back time class 2022-09-20 18:34:43 -04:00
ronaldheft 890852bef5 refactor: Pull out seek back calculation into testable class 2022-09-20 18:32:28 -04:00
advplyrandGitHub 3b285db21a Merge pull request #393 from ronaldheft/fix-on-ready-multiple-calls
fix: onReady called multiple times
2022-09-19 16:46:00 -05:00
advplyrandGitHub d37be88977 Merge pull request #392 from ronaldheft/fix-ios-chapter-sleep-timer
fix: iOS chapter sleep timer issues
2022-09-19 16:45:09 -05:00
ronaldheft e6d27a1487 fix: onReady called multiple times 2022-09-18 14:38:32 -04:00
ronaldheft 5d23c17d30 fix: iOS chapter sleep timer issues 2022-09-18 14:21:41 -04:00
ronaldheft 9c9c1c3b94 fix: Player sometimes pauses during seek 2022-09-18 13:38:16 -04:00
advplyrandGitHub 4e94fd6ad0 Merge pull request #390 from ronaldheft/fix-ios-seek-back-time
fix: iOS seek back time not winding back
2022-09-18 11:58:50 -05:00
advplyrandGitHub 81a8203cca Merge pull request #389 from ronaldheft/ios-bug-artwork
Fix local cover images not used on iOS
2022-09-18 11:57:12 -05:00
ronaldheft b6b206c288 fix: Seek back time not winding back 2022-09-18 12:44:48 -04:00
advplyrandGitHub ed2310a2e7 Merge pull request #387 from ronaldheft/ios-realm-crash
fix: iOS 16 realm crash
2022-09-18 11:29:52 -05:00
ronaldheft bae5d97087 fix: Local artwork not loading 2022-09-18 12:24:19 -04:00
ronaldheft 3e12ff139b fix: Local artwork not used 2022-09-18 11:43:38 -04:00
ronaldheft 7ccd8db085 fix: iOS 16 realm crash 2022-09-17 17:47:18 -04:00
advplyrandGitHub fdc829d06c Merge pull request #386 from ronaldheft/ios-general-fixes
Fix various iOS app startup issues
2022-09-16 16:14:44 -05:00
ronaldheft e7f61e34e8 Fix player automatically starting after WebKit reload 2022-09-15 20:49:25 -04:00
ronaldheft 141877f111 Fix Pods with iOS target change 2022-09-14 20:24:27 -04:00
ronaldheft b71a976e61 Merge remote-tracking branch 'upstream/master' into ios-general-fixes 2022-09-14 19:55:42 -04:00
advplyrandGitHub 68c95ed6d5 Merge pull request #371 from ronaldheft/ios-logging
Improve iOS Logging
2022-09-14 17:54:31 -05:00
ronaldheft 7a68cc99af Convert remain NSLog statement to new logging framework 2022-09-13 21:16:55 -04:00
ronaldheft 7294bc7033 Audio player modifications should be on main queue 2022-09-13 20:47:07 -04:00
ronaldheft f8836be147 fix: Debug logging level does not persist to disk 2022-09-08 20:09:35 -04:00
ronaldheft 713999eb83 Fix deprecated and early window access warnings 2022-09-08 19:51:35 -04:00
ronaldheft cc4c9787c0 Rename ABS to Audiobookshelf 2022-09-06 21:56:10 -04:00
ronaldheft ecc5cf3e1b Rename app to provide better logging 2022-09-06 21:42:12 -04:00
ronaldheft ba7d2be03e Fix incorrect method call 2022-09-06 21:33:24 -04:00
ronaldheft 368f349c78 Use logging framework for AudioPlayer 2022-09-06 21:32:32 -04:00
ronaldheft 639d641c07 Fix usage of this 2022-09-06 21:32:10 -04:00
ronaldheft d9313a1654 Use os logging framework, so logs can be retrieved later 2022-09-06 21:26:48 -04:00
advplyrandGitHub 9dd532285c Merge pull request #365 from vangorra/vangorra/aa-button-cleanup
Cleanup Android Auto buttons
2022-09-04 11:51:11 -05:00
Robert Van GorkomandGitHub 92f10fdb5c Merge branch 'advplyr:master' into vangorra/aa-button-cleanup 2022-09-04 09:23:51 -07:00
advplyrandGitHub 9d0f43a664 Merge pull request #363 from vangorra/vangorra/debug-build
Add debug build to to allow prod and dev apps to work side by side.
2022-09-04 09:46:03 -05:00
Robbie Van Gorkom 453365fcd6 Fix buttons for Android Auto (and probably wear too). 2022-09-03 18:16:12 -07:00
Robbie Van Gorkom 6231c5fcc4 Add debug build to to allow prod and dev apps to work side by side. 2022-09-03 15:06:28 -07:00
89 changed files with 997 additions and 427 deletions
+8 -2
View File
@@ -33,8 +33,8 @@ android {
applicationId "com.audiobookshelf.app"
minSdkVersion rootProject.ext.minSdkVersion
targetSdkVersion rootProject.ext.targetSdkVersion
versionCode 88
versionName "0.9.57-beta"
versionCode 89
versionName "0.9.59-beta"
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
aaptOptions {
// Files and dirs to omit from the packaged assets dir, modified to accommodate modern web apps.
@@ -43,6 +43,12 @@ android {
}
}
buildTypes {
debug {
applicationIdSuffix ".debug"
versionNameSuffix "-debug"
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
}
release {
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
@@ -0,0 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
<background android:drawable="@mipmap/ic_launcher_background"/>
<foreground android:drawable="@mipmap/ic_launcher_foreground"/>
</adaptive-icon>
@@ -0,0 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
<background android:drawable="@mipmap/ic_launcher_background"/>
<foreground android:drawable="@mipmap/ic_launcher_foreground"/>
</adaptive-icon>
Binary file not shown.

After

Width:  |  Height:  |  Size: 5.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 879 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 685 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

@@ -40,7 +40,7 @@ data class DeviceSettings(
@get:JsonIgnore
val jumpBackwardsTimeMs get() = (jumpBackwardsTime ?: default().jumpBackwardsTime) * 1000L
@get:JsonIgnore
val jumpForwardTimeMs get() = (jumpForwardTime ?: default().jumpBackwardsTime) * 1000L
val jumpForwardTimeMs get() = (jumpForwardTime ?: default().jumpForwardTime) * 1000L
}
data class DeviceData(
@@ -239,6 +239,8 @@ class MediaSessionCallback(var playerNotificationService:PlayerNotificationServi
when (action) {
CUSTOM_ACTION_JUMP_FORWARD -> onFastForward()
CUSTOM_ACTION_JUMP_BACKWARD -> onRewind()
CUSTOM_ACTION_SKIP_FORWARD -> onSkipToNext()
CUSTOM_ACTION_SKIP_BACKWARD -> onSkipToPrevious()
}
}
}
@@ -2,3 +2,5 @@ package com.audiobookshelf.app.player
const val CUSTOM_ACTION_JUMP_FORWARD = "com.audiobookshelf.customAction.jump_forward";
const val CUSTOM_ACTION_JUMP_BACKWARD = "com.audiobookshelf.customAction.jump_backward";
const val CUSTOM_ACTION_SKIP_FORWARD = "com.audiobookshelf.customAction.skip_forward";
const val CUSTOM_ACTION_SKIP_BACKWARD = "com.audiobookshelf.customAction.skip_backward";
@@ -45,6 +45,8 @@ import kotlin.concurrent.schedule
const val SLEEP_TIMER_WAKE_UP_EXPIRATION = 120000L // 2m
const val PLAYER_CAST = "cast-player";
const val PLAYER_EXO = "exo-player";
class PlayerNotificationService : MediaBrowserServiceCompat() {
@@ -256,6 +258,10 @@ class PlayerNotificationService : MediaBrowserServiceCompat() {
mediaSessionConnector = MediaSessionConnector(mediaSession)
val queueNavigator: TimelineQueueNavigator = object : TimelineQueueNavigator(mediaSession) {
override fun getSupportedQueueNavigatorActions(player: Player): Long {
return PlaybackStateCompat.ACTION_PLAY_PAUSE or PlaybackStateCompat.ACTION_PLAY or PlaybackStateCompat.ACTION_PAUSE
}
override fun getMediaDescription(player: Player, windowIndex: Int): MediaDescriptionCompat {
if (currentPlaybackSession == null) {
Log.e(tag,"Playback session is not set - returning blank MediaDescriptionCompat")
@@ -296,11 +302,6 @@ class PlayerNotificationService : MediaBrowserServiceCompat() {
mediaSessionConnector.setQueueNavigator(queueNavigator)
mediaSessionConnector.setPlaybackPreparer(MediaSessionPlaybackPreparer(this))
mediaSessionConnector.setCustomActionProviders(
JumpForwardCustomActionProvider(),
JumpBackwardCustomActionProvider(),
)
mediaSession.setCallback(MediaSessionCallback(this))
initializeMPlayer()
@@ -343,19 +344,34 @@ class PlayerNotificationService : MediaBrowserServiceCompat() {
}
isClosed = false
val customActionProviders = mutableListOf(
JumpBackwardCustomActionProvider(),
JumpForwardCustomActionProvider(),
)
val metadata = playbackSession.getMediaMetadataCompat()
mediaSession.setMetadata(metadata)
val mediaItems = playbackSession.getMediaItems()
val playbackRateToUse = playbackRate ?: initialPlaybackRate ?: 1f
initialPlaybackRate = playbackRate
if (playbackSession.mediaPlayer != PLAYER_CAST && mediaItems.size > 1) {
customActionProviders.addAll(listOf(
SkipBackwardCustomActionProvider(),
SkipForwardCustomActionProvider(),
));
}
mediaSessionConnector.setCustomActionProviders(*customActionProviders.toTypedArray());
playbackSession.mediaPlayer = getMediaPlayer()
if (playbackSession.mediaPlayer == "cast-player" && playbackSession.isLocal) {
if (playbackSession.mediaPlayer == PLAYER_CAST && playbackSession.isLocal) {
Log.w(tag, "Cannot cast local media item - switching player")
currentPlaybackSession = null
switchToPlayer(false)
playbackSession.mediaPlayer = getMediaPlayer()
}
if (playbackSession.mediaPlayer == "cast-player") {
if (playbackSession.mediaPlayer == PLAYER_CAST) {
// If cast-player is the first player to be used
mediaSessionConnector.setPlayer(castPlayer)
playerNotificationManager.setPlayer(castPlayer)
@@ -366,10 +382,6 @@ class PlayerNotificationService : MediaBrowserServiceCompat() {
clientEventEmitter?.onPlaybackSession(playbackSession)
val metadata = playbackSession.getMediaMetadataCompat()
mediaSession.setMetadata(metadata)
val mediaItems = playbackSession.getMediaItems()
if (mediaItems.isEmpty()) {
Log.e(tag, "Invalid playback session no media items to play")
currentPlaybackSession = null
@@ -751,7 +763,7 @@ class PlayerNotificationService : MediaBrowserServiceCompat() {
}
fun getMediaPlayer():String {
return if(currentPlayer == castPlayer) "cast-player" else "exo-player"
return if(currentPlayer == castPlayer) PLAYER_CAST else PLAYER_EXO
}
fun getDeviceInfo(): DeviceInfo {
@@ -1024,5 +1036,39 @@ class PlayerNotificationService : MediaBrowserServiceCompat() {
).build()
}
}
inner class SkipForwardCustomActionProvider : CustomActionProvider {
override fun onCustomAction(player: Player, action: String, extras: Bundle?) {
/*
This does not appear to ever get called. Instead, MediaSessionCallback.onCustomAction() is
responsible to reacting to a custom action.
*/
}
override fun getCustomAction(player: Player): PlaybackStateCompat.CustomAction? {
return PlaybackStateCompat.CustomAction.Builder(
CUSTOM_ACTION_SKIP_FORWARD,
getContext().getString(R.string.action_skip_forward),
R.drawable.skip_next_24
).build()
}
}
inner class SkipBackwardCustomActionProvider : CustomActionProvider {
override fun onCustomAction(player: Player, action: String, extras: Bundle?) {
/*
This does not appear to ever get called. Instead, MediaSessionCallback.onCustomAction() is
responsible to reacting to a custom action.
*/
}
override fun getCustomAction(player: Player): PlaybackStateCompat.CustomAction? {
return PlaybackStateCompat.CustomAction.Builder(
CUSTOM_ACTION_SKIP_BACKWARD,
getContext().getString(R.string.action_skip_backward),
R.drawable.skip_previous_24
).build()
}
}
}
@@ -0,0 +1,15 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24"
android:viewportHeight="24"
android:tint="#FFFFFF">
<group android:scaleX="1.127451"
android:scaleY="1.127451"
android:translateX="-1.5294118"
android:translateY="-1.5294118">
<path
android:fillColor="@android:color/white"
android:pathData="M6,18l8.5,-6L6,6v12zM16,6v12h2V6h-2z"/>
</group>
</vector>
@@ -0,0 +1,15 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24"
android:viewportHeight="24"
android:tint="#FFFFFF">
<group android:scaleX="1.127451"
android:scaleY="1.127451"
android:translateX="-1.5294118"
android:translateY="-1.5294118">
<path
android:fillColor="@android:color/white"
android:pathData="M6,6h2v12L6,18zM9.5,12l8.5,6L18,6z"/>
</group>
</vector>
@@ -0,0 +1,10 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24"
android:viewportHeight="24"
android:tint="#FFFFFF">
<path
android:fillColor="@android:color/white"
android:pathData="M6,18l8.5,-6L6,6v12zM16,6v12h2V6h-2z"/>
</vector>
@@ -0,0 +1,10 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24"
android:viewportHeight="24"
android:tint="#FFFFFF">
<path
android:fillColor="@android:color/white"
android:pathData="M6,6h2v12L6,18zM9.5,12l8.5,6L18,6z"/>
</vector>
@@ -11,6 +11,8 @@
<item name="windowActionBar">false</item>
<item name="windowNoTitle">true</item>
<item name="android:background">@null</item>
<item name="android:statusBarColor">@color/background_dark</item>
<item name="android:navigationBarColor">@color/background_dark</item>
</style>
@@ -3,4 +3,5 @@
<color name="light_blue_200">#FF81D4FA</color>
<color name="light_blue_600">#FF039BE5</color>
<color name="light_blue_900">#FF01579B</color>
<color name="background_dark">#262626</color>
</resources>
@@ -8,4 +8,6 @@
<string name="app_widget_description">Simple widget for audiobookshelf playback</string>
<string name="action_jump_forward">Jump Forward</string>
<string name="action_jump_backward">Jump Backward</string>
<string name="action_skip_forward">Skip Forward</string>
<string name="action_skip_backward">Skip Backward</string>
</resources>
@@ -13,6 +13,8 @@
<item name="windowActionBar">false</item>
<item name="windowNoTitle">true</item>
<item name="android:background">@null</item>
<item name="android:statusBarColor">@color/background_dark</item>
<item name="android:navigationBarColor">@color/background_dark</item>
</style>
+106
View File
@@ -0,0 +1,106 @@
@font-face {
font-family: 'absicons';
src: url('~static/fonts/absicons/absicons.eot?2jfq33');
src: url('~static/fonts/absicons/absicons.eot?2jfq33#iefix') format('embedded-opentype'),
url('~static/fonts/absicons/absicons.ttf?2jfq33') format('truetype'),
url('~static/fonts/absicons/absicons.woff?2jfq33') format('woff'),
url('~static/fonts/absicons/absicons.svg?2jfq33#absicons') format('svg');
font-weight: normal;
font-style: normal;
font-display: block;
}
.abs-icons {
/* use !important to prevent issues with browser extensions that change fonts */
font-family: 'absicons' !important;
speak: never;
font-style: normal;
font-weight: normal;
font-variant: normal;
text-transform: none;
line-height: 1;
/* Better Font Rendering =========== */
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
}
.icon-books-1:before {
content: "\e905";
}
.icon-microphone-1:before {
content: "\e902";
}
.icon-radio:before {
content: "\e903";
}
.icon-podcast:before {
content: "\e904";
}
.icon-audiobookshelf:before {
content: "\e900";
}
.icon-database:before {
content: "\e906";
}
.icon-microphone-2:before {
content: "\e901";
}
.icon-headphones:before {
content: "\e910";
}
.icon-music:before {
content: "\e911";
}
.icon-video:before {
content: "\e914";
}
.icon-microphone-3:before {
content: "\e91e";
}
.icon-book-1:before {
content: "\e91f";
}
.icon-books-2:before {
content: "\e920";
}
.icon-file-picture:before {
content: "\e927";
}
.icon-database-1:before {
content: "\e964";
}
.icon-rocket:before {
content: "\e9a5";
}
.icon-power:before {
content: "\e9b5";
}
.icon-star:before {
content: "\e9d9";
}
.icon-heart:before {
content: "\e9da";
}
.icon-rss:before {
content: "\ea9b";
}
+1
View File
@@ -1,5 +1,6 @@
@import "./fonts.css";
@import './defaultStyles.css';
@import './absicons.css';
body {
background-color: #262626;
+1 -1
View File
@@ -9,7 +9,7 @@
</a>
<div v-if="user && currentLibrary">
<div class="pl-1.5 pr-2.5 py-2 bg-bg bg-opacity-30 rounded-md flex items-center" @click="clickShowLibraryModal">
<widgets-library-icon :icon="currentLibraryIcon" :size="4" />
<ui-library-icon :icon="currentLibraryIcon" :size="4" font-size="base" />
<p class="text-sm font-book leading-4 ml-2 mt-0.5 max-w-24 truncate">{{ currentLibraryName }}</p>
</div>
</div>
+1 -1
View File
@@ -205,7 +205,7 @@ export default {
return this.$store.getters['getJumpBackwardsTime']
},
bookCoverAspectRatio() {
return this.$store.getters['getBookCoverAspectRatio']
return this.$store.getters['libraries/getBookCoverAspectRatio']
},
bookCoverWidth() {
if (this.showFullscreen) return this.fullscreenBookCoverWidth
+4 -2
View File
@@ -155,8 +155,10 @@ export default {
}
// Settings have been loaded (at least once, so it's safe to kickoff onReady)
this.settingsLoaded = true
this.notifyOnReady()
if (!this.settingsLoaded) {
this.settingsLoaded = true
this.notifyOnReady()
}
},
setListeners() {
// if (!this.$server.socket) {
+1 -1
View File
@@ -89,7 +89,7 @@ export default {
return this.bookCoverAspectRatio === 1
},
bookCoverAspectRatio() {
return this.$store.getters['getBookCoverAspectRatio']
return this.$store.getters['libraries/getBookCoverAspectRatio']
},
bookWidth() {
var coverSize = 100
+1 -1
View File
@@ -69,7 +69,7 @@ export default {
return this.bookCoverAspectRatio === 1
},
bookCoverAspectRatio() {
return this.$store.getters['getBookCoverAspectRatio']
return this.$store.getters['libraries/getBookCoverAspectRatio']
},
altViewEnabled() {
return this.$store.getters['getAltViewEnabled']
+1 -1
View File
@@ -31,7 +31,7 @@ export default {
},
computed: {
bookCoverAspectRatio() {
return this.$store.getters['getBookCoverAspectRatio']
return this.$store.getters['libraries/getBookCoverAspectRatio']
},
coverWidth() {
if (this.bookCoverAspectRatio === 1) return 50 * 1.2
+1 -1
View File
@@ -24,7 +24,7 @@ export default {
},
computed: {
bookCoverAspectRatio() {
return this.$store.getters['getBookCoverAspectRatio']
return this.$store.getters['libraries/getBookCoverAspectRatio']
},
name() {
return this.series.name
-23
View File
@@ -1,23 +0,0 @@
<template>
<svg fill="currentColor" class="h-full w-full p-px" viewBox="0 0 1978.03 2349.44">
<path
d="M2519.5,1438.39c-12.13-10.1-31-25-56.57-42.62V1197.31c0-505.94-410.15-916.09-916.1-916.09h0c-505.94,0-916.09,410.15-916.09,916.09v198.46c-25.57,17.66-44.44,32.52-56.57,42.62a45.45,45.45,0,0,0-16.35,34.95v237.74a45.45,45.45,0,0,0,16.35,35c28.28,23.54,93.18,72.92,194.22,123.55v23.11c0,62.32,40.21,112.85,89.8,112.85h0c49.59,0,89.8-50.53,89.8-112.85V1322.51c0-62.33-40.21-112.86-89.8-112.86h0c-47.51,0-86.4,46.38-89.58,105.07l-.22.11V1197.31c0-429.92,348.52-778.43,778.44-778.43h0c429.92,0,778.44,348.51,778.44,778.43v117.52l-.22-.11c-3.18-58.69-42.06-105.07-89.58-105.07h0c-49.59,0-89.79,50.53-89.79,112.86v570.18c0,62.32,40.2,112.85,89.79,112.85h0c49.6,0,89.8-50.53,89.8-112.85v-23.11c101.05-50.63,165.95-100,194.23-123.55a45.48,45.48,0,0,0,16.35-35V1473.34A45.48,45.48,0,0,0,2519.5,1438.39Z"
transform="translate(-557.82 -281.22)"
/>
<path d="M1227.4,2429.63a108.47,108.47,0,0,0,108.47-108.47V1106.56A108.47,108.47,0,0,0,1227.4,998.08H1115.33a108.48,108.48,0,0,0-108.48,108.48v1214.6a108.47,108.47,0,0,0,108.48,108.47ZM1047.75,1289.38H1295v25.83H1047.75Z" transform="translate(-557.82 -281.22)" />
<path d="M1602.87,2429.63a108.47,108.47,0,0,0,108.47-108.47V1106.56a108.47,108.47,0,0,0-108.47-108.48H1490.8a108.48,108.48,0,0,0-108.48,108.48v1214.6a108.47,108.47,0,0,0,108.48,108.47ZM1423.22,1289.38h247.22v25.83H1423.22Z" transform="translate(-557.82 -281.22)" />
<path d="M1978.34,2429.63a108.47,108.47,0,0,0,108.47-108.47V1106.56a108.47,108.47,0,0,0-108.47-108.48H1866.27a108.48,108.48,0,0,0-108.48,108.48v1214.6a108.47,108.47,0,0,0,108.48,108.47ZM1798.69,1289.38h247.22v25.83H1798.69Z" transform="translate(-557.82 -281.22)" />
<rect x="180.05" y="2185.95" width="1617.93" height="163.49" rx="81.74" />
</svg>
</template>
<script>
export default {
data() {
return {}
},
computed: {},
methods: {},
mounted() {}
}
</script>
-16
View File
@@ -1,16 +0,0 @@
<template>
<svg fill="currentColor" viewBox="0 0 24 24">
<path fill="currentColor" d="M9 3V18H12V3H9M12 5L16 18L19 17L15 4L12 5M5 5V18H8V5H5M3 19V21H21V19H3Z" />
</svg>
</template>
<script>
export default {
data() {
return {}
},
computed: {},
methods: {},
mounted() {}
}
</script>
-16
View File
@@ -1,16 +0,0 @@
<template>
<svg viewBox="0 0 24 24">
<path fill="currentColor" d="M6,19L9,15.14L11.14,17.72L14.14,13.86L18,19H6M6,4H11V12L8.5,10.5L6,12M18,2H6A2,2 0 0,0 4,4V20A2,2 0 0,0 6,22H18A2,2 0 0,0 20,20V4A2,2 0 0,0 18,2Z" />
</svg>
</template>
<script>
export default {
data() {
return {}
},
computed: {},
methods: {},
mounted() {}
}
</script>
-16
View File
@@ -1,16 +0,0 @@
<template>
<svg class="p-px" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 7v10c0 2.21 3.582 4 8 4s8-1.79 8-4V7M4 7c0 2.21 3.582 4 8 4s8-1.79 8-4M4 7c0-2.21 3.582-4 8-4s8 1.79 8 4m0 5c0 2.21-3.582 4-8 4s-8-1.79-8-4" />
</svg>
</template>
<script>
export default {
data() {
return {}
},
computed: {},
methods: {},
mounted() {}
}
</script>
-19
View File
@@ -1,19 +0,0 @@
<template>
<svg class="p-px" viewBox="0 0 122.877 120.596">
<path
fill="currentColor"
d="M68.925,69.906v50.689H53.953V69.906c-4.918-2.662-8.259-7.867-8.259-13.854 c0-8.694,7.05-15.744,15.745-15.744c8.694,0,15.745,7.05,15.745,15.744C77.184,62.039,73.843,67.244,68.925,69.906L68.925,69.906z M39.32,11.165c2.916-1.438,4.111-4.969,2.673-7.882c-1.438-2.914-4.966-4.111-7.88-2.674C22.213,6.479,12.958,16.19,7.11,27.625 c-4.32,8.445-6.783,17.842-7.08,27.325c-0.299,9.563,1.587,19.223,5.973,28.114c5.401,10.953,14.558,20.695,28.039,27.592 c2.889,1.477,6.429,0.33,7.905-2.559c1.477-2.889,0.331-6.428-2.558-7.904c-11.037-5.645-18.486-13.525-22.833-22.334 c-3.506-7.111-5.014-14.857-4.774-22.539c0.243-7.757,2.256-15.442,5.79-22.348C22.304,23.721,29.76,15.879,39.32,11.165 L39.32,11.165z M88.765,0.608c-2.914-1.438-6.443-0.24-7.881,2.674c-1.438,2.914-0.242,6.445,2.674,7.882 c9.561,4.715,17.017,12.556,21.747,21.808c3.533,6.905,5.547,14.59,5.789,22.348c0.24,7.682-1.268,15.428-4.773,22.539 c-4.347,8.809-11.796,16.689-22.833,22.334c-2.889,1.477-4.034,5.016-2.558,7.904c1.476,2.889,5.016,4.035,7.905,2.559 c13.48-6.896,22.638-16.639,28.039-27.592c4.386-8.891,6.272-18.551,5.973-28.114c-0.297-9.483-2.76-18.88-7.079-27.325 C109.919,16.19,100.665,6.479,88.765,0.608L88.765,0.608z M82.791,26.505c-2.195-1.581-5.256-1.082-6.837,1.113 c-1.58,2.195-1.082,5.256,1.113,6.837c0.885,0.637,1.753,1.352,2.604,2.134c4.971,4.583,7.919,10.694,8.538,17.16 c0.626,6.524-1.111,13.437-5.518,19.552c-0.748,1.039-1.61,2.092-2.585,3.15c-1.835,1.992-1.708,5.098,0.287,6.932 c1.994,1.834,5.099,1.705,6.933-0.287c1.18-1.279,2.286-2.641,3.315-4.072c5.862-8.139,8.166-17.4,7.322-26.197 c-0.848-8.853-4.871-17.208-11.648-23.457C85.249,28.387,84.074,27.431,82.791,26.505L82.791,26.505z M45.81,34.458 c2.195-1.581,2.694-4.642,1.113-6.837c-1.581-2.195-4.642-2.694-6.837-1.114c-1.284,0.926-2.458,1.882-3.524,2.864 c-6.778,6.25-10.801,14.604-11.649,23.457c-0.844,8.796,1.46,18.06,7.323,26.199c1.031,1.43,2.136,2.791,3.315,4.07 c1.834,1.992,4.939,2.121,6.932,0.287c1.996-1.834,2.123-4.939,0.288-6.932c-0.975-1.059-1.837-2.111-2.585-3.15 c-4.406-6.115-6.144-13.027-5.518-19.551c0.619-6.465,3.567-12.577,8.538-17.16C44.058,35.81,44.926,35.095,45.81,34.458 L45.81,34.458z"
/>
</svg>
</template>
<script>
export default {
data() {
return {}
},
computed: {},
methods: {},
mounted() {}
}
</script>
+1 -1
View File
@@ -40,7 +40,7 @@ export default {
}
},
bookCoverAspectRatio() {
return this.$store.getters['getBookCoverAspectRatio']
return this.$store.getters['libraries/getBookCoverAspectRatio']
}
},
methods: {
+1 -1
View File
@@ -13,7 +13,7 @@
<li :key="library.id" class="text-gray-50 select-none relative py-3 cursor-pointer hover:bg-black-400" :class="currentLibraryId === library.id ? 'bg-bg bg-opacity-80' : ''" role="option" @click="clickedOption(library)">
<div v-show="currentLibraryId === library.id" class="absolute top-0 left-0 w-0.5 bg-warning h-full" />
<div class="flex items-center px-3">
<widgets-library-icon :icon="library.icon" />
<ui-library-icon :icon="library.icon" />
<span class="font-normal block truncate text-lg ml-4">{{ library.name }}</span>
</div>
</li>
+27 -2
View File
@@ -39,6 +39,7 @@
<script>
import Path from 'path'
import { Archive } from 'libarchive.js/main.js'
import { CompressedFile } from 'libarchive.js/src/compressed-file'
Archive.init({
workerUrl: '/libarchive/worker-bundle.js'
@@ -136,7 +137,11 @@ export default {
responseType: 'blob'
})
const archive = await Archive.open(buff)
this.filesObject = await archive.getFilesObject()
const originalFilesObject = await archive.getFilesObject()
// to support images in subfolders we need to flatten the object
// ref: https://github.com/advplyr/audiobookshelf/issues/811
this.filesObject = this.flattenFilesObject(originalFilesObject)
console.log('Extracted files object', this.filesObject)
var filenames = Object.keys(this.filesObject)
this.parseFilenames(filenames)
@@ -154,6 +159,26 @@ export default {
this.loading = false
}
},
flattenFilesObject(filesObject) {
const flattenObject = (obj, prefix = '') => {
var _obj = {}
for (const key in obj) {
const newKey = prefix ? prefix + '/' + key : key
if (obj[key] instanceof CompressedFile) {
_obj[newKey] = obj[key]
} else if (!key.startsWith('_') && typeof obj[key] === 'object' && !Array.isArray(obj[key])) {
_obj = {
..._obj,
...flattenObject(obj[key], newKey)
}
} else {
_obj[newKey] = obj[key]
}
}
return _obj
}
return flattenObject(filesObject)
},
async extractXmlFile(filename) {
console.log('extracting xml filename', filename)
try {
@@ -173,7 +198,7 @@ export default {
},
parseImageFilename(filename) {
var basename = Path.basename(filename, Path.extname(filename))
var numbersinpath = basename.match(/\d{1,4}/g)
var numbersinpath = basename.match(/\d{1,5}/g)
if (!numbersinpath || !numbersinpath.length) {
return {
index: -1,
@@ -50,7 +50,7 @@ export default {
return this.$elapsedPretty(this.media.duration)
},
bookCoverAspectRatio() {
return this.$store.getters['getBookCoverAspectRatio']
return this.$store.getters['libraries/getBookCoverAspectRatio']
},
bookWidth() {
if (this.bookCoverAspectRatio === 1) return 50
+37
View File
@@ -0,0 +1,37 @@
<template>
<div :class="`h-${size} w-${size} min-w-${size} text-${fontSize}`" class="flex items-center justify-center">
<span class="abs-icons" :class="`icon-${iconToUse}`"></span>
</div>
</template>
<script>
export default {
props: {
icon: {
type: String,
default: 'audiobookshelf'
},
fontSize: {
type: String,
default: 'lg'
},
size: {
type: Number,
default: 5
}
},
data() {
return {}
},
computed: {
iconToUse() {
return this.icons.includes(this.icon) ? this.icon : 'audiobookshelf'
},
icons() {
return this.$store.state.globals.libraryIcons
}
},
methods: {},
mounted() {}
}
</script>
-28
View File
@@ -1,28 +0,0 @@
<template>
<div :class="`h-${size} w-${size}`">
<component :is="iconComponentName" />
</div>
</template>
<script>
export default {
props: {
icon: String,
size: {
type: Number,
default: 5
}
},
data() {
return {}
},
computed: {
iconComponentName() {
if (this.icon === 'default') return `icons-database-svg`
return `icons-${this.icon}-svg`
}
},
methods: {},
mounted() {}
}
</script>
+194 -21
View File
@@ -34,7 +34,7 @@
504EC30F1FED79650016851F /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 504EC30E1FED79650016851F /* Assets.xcassets */; };
504EC3121FED79650016851F /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 504EC3101FED79650016851F /* LaunchScreen.storyboard */; };
50B271D11FEDC1A000F3C39B /* public in Resources */ = {isa = PBXBuildFile; fileRef = 50B271D01FEDC1A000F3C39B /* public */; };
A084ECDBA7D38E1E42DFC39D /* Pods_App.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = AF277DCFFFF123FFC6DF26C7 /* Pods_App.framework */; };
AC98F95F300E46A27FAE47A4 /* Pods_Audiobookshelf.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = B775656E49492CFD6763B7B6 /* Pods_Audiobookshelf.framework */; };
E9D5504628AC1A3900C746DD /* LibraryItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = E9D5504528AC1A3900C746DD /* LibraryItem.swift */; };
E9D5504828AC1A7A00C746DD /* MediaType.swift in Sources */ = {isa = PBXBuildFile; fileRef = E9D5504728AC1A7A00C746DD /* MediaType.swift */; };
E9D5504A28AC1AA600C746DD /* Metadata.swift in Sources */ = {isa = PBXBuildFile; fileRef = E9D5504928AC1AA600C746DD /* Metadata.swift */; };
@@ -59,9 +59,22 @@
E9D5507328AC218300C746DD /* DaoExtensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = E9D5507228AC218300C746DD /* DaoExtensions.swift */; };
E9D5507528AEF93100C746DD /* PlayerSettings.swift in Sources */ = {isa = PBXBuildFile; fileRef = E9D5507428AEF93100C746DD /* PlayerSettings.swift */; };
E9DFCBFB28C28F4A00B36356 /* AudioPlayerSleepTimer.swift in Sources */ = {isa = PBXBuildFile; fileRef = E9DFCBFA28C28F4A00B36356 /* AudioPlayerSleepTimer.swift */; };
E9E8814A28DA644F00D750C1 /* PlayerTimeUtils.swift in Sources */ = {isa = PBXBuildFile; fileRef = E9E8814928DA644F00D750C1 /* PlayerTimeUtils.swift */; };
E9E8814D28DA6B9000D750C1 /* PlayerTimeUtilsTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = E9E8814C28DA6B9000D750C1 /* PlayerTimeUtilsTests.swift */; };
E9E985F828B02D9400957F23 /* PlayerProgress.swift in Sources */ = {isa = PBXBuildFile; fileRef = E9E985F728B02D9400957F23 /* PlayerProgress.swift */; };
E9FA07E328C82848005520B0 /* Logger.swift in Sources */ = {isa = PBXBuildFile; fileRef = E9FA07E228C82848005520B0 /* Logger.swift */; };
/* End PBXBuildFile section */
/* Begin PBXContainerItemProxy section */
E9E8813E28DA5DE500D750C1 /* PBXContainerItemProxy */ = {
isa = PBXContainerItemProxy;
containerPortal = 504EC2FC1FED79650016851F /* Project object */;
proxyType = 1;
remoteGlobalIDString = 504EC3031FED79650016851F;
remoteInfo = Audiobookshelf;
};
/* End PBXContainerItemProxy section */
/* Begin PBXFileReference section */
2FAD9762203C412B000D30F8 /* config.xml */ = {isa = PBXFileReference; lastKnownFileType = text.xml; path = config.xml; sourceTree = "<group>"; };
3A200C1427D64D7E00CBF02E /* AudioPlayer.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AudioPlayer.swift; sourceTree = "<group>"; };
@@ -86,15 +99,17 @@
4D8D412C26E187E400BA5F0D /* App-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "App-Bridging-Header.h"; sourceTree = "<group>"; };
4DF74911287105C600AC7814 /* DeviceSettings.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DeviceSettings.swift; sourceTree = "<group>"; };
50379B222058CBB4000EE86E /* capacitor.config.json */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.json; path = capacitor.config.json; sourceTree = "<group>"; };
504EC3041FED79650016851F /* App.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = App.app; sourceTree = BUILT_PRODUCTS_DIR; };
504EC3041FED79650016851F /* Audiobookshelf.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Audiobookshelf.app; sourceTree = BUILT_PRODUCTS_DIR; };
504EC3071FED79650016851F /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = "<group>"; };
504EC30C1FED79650016851F /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = "<group>"; };
504EC30E1FED79650016851F /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = "<group>"; };
504EC3111FED79650016851F /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = "<group>"; };
504EC3131FED79650016851F /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
50B271D01FEDC1A000F3C39B /* public */ = {isa = PBXFileReference; lastKnownFileType = folder; path = public; sourceTree = "<group>"; };
AF277DCFFFF123FFC6DF26C7 /* Pods_App.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_App.framework; sourceTree = BUILT_PRODUCTS_DIR; };
5F7EC3E03050241EA35154B3 /* Pods-Audiobookshelf.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Audiobookshelf.release.xcconfig"; path = "Pods/Target Support Files/Pods-Audiobookshelf/Pods-Audiobookshelf.release.xcconfig"; sourceTree = "<group>"; };
8FBD5FE4076DDFA0DA782256 /* Pods-Audiobookshelf.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Audiobookshelf.debug.xcconfig"; path = "Pods/Target Support Files/Pods-Audiobookshelf/Pods-Audiobookshelf.debug.xcconfig"; sourceTree = "<group>"; };
AF51FD2D460BCFE21FA515B2 /* Pods-App.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-App.release.xcconfig"; path = "Pods/Target Support Files/Pods-App/Pods-App.release.xcconfig"; sourceTree = "<group>"; };
B775656E49492CFD6763B7B6 /* Pods_Audiobookshelf.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_Audiobookshelf.framework; sourceTree = BUILT_PRODUCTS_DIR; };
E9D5504528AC1A3900C746DD /* LibraryItem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LibraryItem.swift; sourceTree = "<group>"; };
E9D5504728AC1A7A00C746DD /* MediaType.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MediaType.swift; sourceTree = "<group>"; };
E9D5504928AC1AA600C746DD /* Metadata.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Metadata.swift; sourceTree = "<group>"; };
@@ -119,7 +134,11 @@
E9D5507228AC218300C746DD /* DaoExtensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DaoExtensions.swift; sourceTree = "<group>"; };
E9D5507428AEF93100C746DD /* PlayerSettings.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PlayerSettings.swift; sourceTree = "<group>"; };
E9DFCBFA28C28F4A00B36356 /* AudioPlayerSleepTimer.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AudioPlayerSleepTimer.swift; sourceTree = "<group>"; };
E9E8813A28DA5DE500D750C1 /* AudiobookshelfUnitTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = AudiobookshelfUnitTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; };
E9E8814928DA644F00D750C1 /* PlayerTimeUtils.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PlayerTimeUtils.swift; sourceTree = "<group>"; };
E9E8814C28DA6B9000D750C1 /* PlayerTimeUtilsTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PlayerTimeUtilsTests.swift; sourceTree = "<group>"; };
E9E985F728B02D9400957F23 /* PlayerProgress.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PlayerProgress.swift; sourceTree = "<group>"; };
E9FA07E228C82848005520B0 /* Logger.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Logger.swift; sourceTree = "<group>"; };
FC68EB0AF532CFC21C3344DD /* Pods-App.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-App.debug.xcconfig"; path = "Pods/Target Support Files/Pods-App/Pods-App.debug.xcconfig"; sourceTree = "<group>"; };
/* End PBXFileReference section */
@@ -128,7 +147,14 @@
isa = PBXFrameworksBuildPhase;
buildActionMask = 2147483647;
files = (
A084ECDBA7D38E1E42DFC39D /* Pods_App.framework in Frameworks */,
AC98F95F300E46A27FAE47A4 /* Pods_Audiobookshelf.framework in Frameworks */,
);
runOnlyForDeploymentPostprocessing = 0;
};
E9E8813728DA5DE500D750C1 /* Frameworks */ = {
isa = PBXFrameworksBuildPhase;
buildActionMask = 2147483647;
files = (
);
runOnlyForDeploymentPostprocessing = 0;
};
@@ -138,7 +164,7 @@
27E2DDA53C4D2A4D1A88CE4A /* Frameworks */ = {
isa = PBXGroup;
children = (
AF277DCFFFF123FFC6DF26C7 /* Pods_App.framework */,
B775656E49492CFD6763B7B6 /* Pods_Audiobookshelf.framework */,
);
name = Frameworks;
sourceTree = "<group>";
@@ -146,6 +172,7 @@
3ABF6190280432610070250E /* player */ = {
isa = PBXGroup;
children = (
E9E8814828DA641B00D750C1 /* util */,
3A200C1427D64D7E00CBF02E /* AudioPlayer.swift */,
E9DFCBFA28C28F4A00B36356 /* AudioPlayerSleepTimer.swift */,
3ABF618E2804325C0070250E /* PlayerHandler.swift */,
@@ -199,6 +226,7 @@
3AFCB5E627EA23F700ECCC05 /* util */ = {
isa = PBXGroup;
children = (
E9FA07E228C82848005520B0 /* Logger.swift */,
3AFCB5E727EA240D00ECCC05 /* NowPlayingInfo.swift */,
3AD4FCEA280443DD006DB301 /* Database.swift */,
3AD4FCEC28044E6C006DB301 /* Store.swift */,
@@ -215,6 +243,7 @@
children = (
3AC8248B27F2316900529205 /* Shared */,
504EC3061FED79650016851F /* App */,
E9E8813B28DA5DE500D750C1 /* AudiobookshelfUnitTests */,
504EC3051FED79650016851F /* Products */,
7F8756D8B27F46E3366F6CEA /* Pods */,
27E2DDA53C4D2A4D1A88CE4A /* Frameworks */,
@@ -224,7 +253,8 @@
504EC3051FED79650016851F /* Products */ = {
isa = PBXGroup;
children = (
504EC3041FED79650016851F /* App.app */,
504EC3041FED79650016851F /* Audiobookshelf.app */,
E9E8813A28DA5DE500D750C1 /* AudiobookshelfUnitTests.xctest */,
);
name = Products;
sourceTree = "<group>";
@@ -251,6 +281,8 @@
children = (
FC68EB0AF532CFC21C3344DD /* Pods-App.debug.xcconfig */,
AF51FD2D460BCFE21FA515B2 /* Pods-App.release.xcconfig */,
8FBD5FE4076DDFA0DA782256 /* Pods-Audiobookshelf.debug.xcconfig */,
5F7EC3E03050241EA35154B3 /* Pods-Audiobookshelf.release.xcconfig */,
);
name = Pods;
sourceTree = "<group>";
@@ -295,12 +327,52 @@
path = download;
sourceTree = "<group>";
};
E9E8813B28DA5DE500D750C1 /* AudiobookshelfUnitTests */ = {
isa = PBXGroup;
children = (
E9E8814328DA5E5900D750C1 /* Shared */,
);
path = AudiobookshelfUnitTests;
sourceTree = "<group>";
};
E9E8814328DA5E5900D750C1 /* Shared */ = {
isa = PBXGroup;
children = (
E9E8814428DA5E6000D750C1 /* player */,
);
path = Shared;
sourceTree = "<group>";
};
E9E8814428DA5E6000D750C1 /* player */ = {
isa = PBXGroup;
children = (
E9E8814B28DA6B6B00D750C1 /* util */,
);
path = player;
sourceTree = "<group>";
};
E9E8814828DA641B00D750C1 /* util */ = {
isa = PBXGroup;
children = (
E9E8814928DA644F00D750C1 /* PlayerTimeUtils.swift */,
);
path = util;
sourceTree = "<group>";
};
E9E8814B28DA6B6B00D750C1 /* util */ = {
isa = PBXGroup;
children = (
E9E8814C28DA6B9000D750C1 /* PlayerTimeUtilsTests.swift */,
);
path = util;
sourceTree = "<group>";
};
/* End PBXGroup section */
/* Begin PBXNativeTarget section */
504EC3031FED79650016851F /* App */ = {
504EC3031FED79650016851F /* Audiobookshelf */ = {
isa = PBXNativeTarget;
buildConfigurationList = 504EC3161FED79650016851F /* Build configuration list for PBXNativeTarget "App" */;
buildConfigurationList = 504EC3161FED79650016851F /* Build configuration list for PBXNativeTarget "Audiobookshelf" */;
buildPhases = (
6634F4EFEBD30273BCE97C65 /* [CP] Check Pods Manifest.lock */,
504EC3001FED79650016851F /* Sources */,
@@ -312,18 +384,36 @@
);
dependencies = (
);
name = App;
name = Audiobookshelf;
productName = App;
productReference = 504EC3041FED79650016851F /* App.app */;
productReference = 504EC3041FED79650016851F /* Audiobookshelf.app */;
productType = "com.apple.product-type.application";
};
E9E8813928DA5DE500D750C1 /* AudiobookshelfUnitTests */ = {
isa = PBXNativeTarget;
buildConfigurationList = E9E8814228DA5DE500D750C1 /* Build configuration list for PBXNativeTarget "AudiobookshelfUnitTests" */;
buildPhases = (
E9E8813628DA5DE500D750C1 /* Sources */,
E9E8813728DA5DE500D750C1 /* Frameworks */,
E9E8813828DA5DE500D750C1 /* Resources */,
);
buildRules = (
);
dependencies = (
E9E8813F28DA5DE500D750C1 /* PBXTargetDependency */,
);
name = AudiobookshelfUnitTests;
productName = AudiobookshelfUnitTests;
productReference = E9E8813A28DA5DE500D750C1 /* AudiobookshelfUnitTests.xctest */;
productType = "com.apple.product-type.bundle.unit-test";
};
/* End PBXNativeTarget section */
/* Begin PBXProject section */
504EC2FC1FED79650016851F /* Project object */ = {
isa = PBXProject;
attributes = {
LastSwiftUpdateCheck = 920;
LastSwiftUpdateCheck = 1400;
LastUpgradeCheck = 1330;
TargetAttributes = {
504EC3031FED79650016851F = {
@@ -331,6 +421,11 @@
LastSwiftMigration = 1240;
ProvisioningStyle = Automatic;
};
E9E8813928DA5DE500D750C1 = {
CreatedOnToolsVersion = 14.0;
ProvisioningStyle = Automatic;
TestTargetID = 504EC3031FED79650016851F;
};
};
};
buildConfigurationList = 504EC2FF1FED79650016851F /* Build configuration list for PBXProject "App" */;
@@ -346,7 +441,8 @@
projectDirPath = "";
projectRoot = "";
targets = (
504EC3031FED79650016851F /* App */,
504EC3031FED79650016851F /* Audiobookshelf */,
E9E8813928DA5DE500D750C1 /* AudiobookshelfUnitTests */,
);
};
/* End PBXProject section */
@@ -365,6 +461,13 @@
);
runOnlyForDeploymentPostprocessing = 0;
};
E9E8813828DA5DE500D750C1 /* Resources */ = {
isa = PBXResourcesBuildPhase;
buildActionMask = 2147483647;
files = (
);
runOnlyForDeploymentPostprocessing = 0;
};
/* End PBXResourcesBuildPhase section */
/* Begin PBXShellScriptBuildPhase section */
@@ -379,7 +482,7 @@
);
name = "[CP] Check Pods Manifest.lock";
outputPaths = (
"$(DERIVED_FILE_DIR)/Pods-App-checkManifestLockResult.txt",
"$(DERIVED_FILE_DIR)/Pods-Audiobookshelf-checkManifestLockResult.txt",
);
runOnlyForDeploymentPostprocessing = 0;
shellPath = /bin/sh;
@@ -398,7 +501,7 @@
);
runOnlyForDeploymentPostprocessing = 0;
shellPath = /bin/sh;
shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-App/Pods-App-frameworks.sh\"\n";
shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-Audiobookshelf/Pods-Audiobookshelf-frameworks.sh\"\n";
showEnvVarsInLog = 0;
};
/* End PBXShellScriptBuildPhase section */
@@ -412,6 +515,7 @@
E9D5506228AC1CC900C746DD /* PlayerState.swift in Sources */,
3AD4FCE728043E72006DB301 /* AbsDatabase.m in Sources */,
504EC3081FED79650016851F /* AppDelegate.swift in Sources */,
E9FA07E328C82848005520B0 /* Logger.swift in Sources */,
3A90295F280968E700E1D427 /* PlaybackReport.swift in Sources */,
E9D5505A28AC1C4500C746DD /* Folder.swift in Sources */,
3ABF580928059BAE005DFBE5 /* PlaybackSession.swift in Sources */,
@@ -452,13 +556,30 @@
3AF1970C2806E2590096F747 /* ApiClient.swift in Sources */,
4D66B954282EE87C008272D4 /* AbsDownloader.swift in Sources */,
E9D5505628AC1BFA00C746DD /* FileMetadata.swift in Sources */,
E9E8814A28DA644F00D750C1 /* PlayerTimeUtils.swift in Sources */,
3AB34055280832720039308B /* PlayerEvents.swift in Sources */,
E9D5506C28AC1E2100C746DD /* LocalMediaProgress.swift in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
E9E8813628DA5DE500D750C1 /* Sources */ = {
isa = PBXSourcesBuildPhase;
buildActionMask = 2147483647;
files = (
E9E8814D28DA6B9000D750C1 /* PlayerTimeUtilsTests.swift in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
/* End PBXSourcesBuildPhase section */
/* Begin PBXTargetDependency section */
E9E8813F28DA5DE500D750C1 /* PBXTargetDependency */ = {
isa = PBXTargetDependency;
target = 504EC3031FED79650016851F /* Audiobookshelf */;
targetProxy = E9E8813E28DA5DE500D750C1 /* PBXContainerItemProxy */;
};
/* End PBXTargetDependency section */
/* Begin PBXVariantGroup section */
504EC30B1FED79650016851F /* Main.storyboard */ = {
isa = PBXVariantGroup;
@@ -594,17 +715,17 @@
};
504EC3171FED79650016851F /* Debug */ = {
isa = XCBuildConfiguration;
baseConfigurationReference = FC68EB0AF532CFC21C3344DD /* Pods-App.debug.xcconfig */;
baseConfigurationReference = 8FBD5FE4076DDFA0DA782256 /* Pods-Audiobookshelf.debug.xcconfig */;
buildSettings = {
ASSETCATALOG_COMPILER_APPICON_NAME = Icons;
CLANG_ENABLE_MODULES = YES;
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 15;
CURRENT_PROJECT_VERSION = 16;
DEVELOPMENT_TEAM = 7UFJ7D8V6A;
INFOPLIST_FILE = App/Info.plist;
IPHONEOS_DEPLOYMENT_TARGET = 14.0;
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks";
MARKETING_VERSION = 0.9.58;
MARKETING_VERSION = 0.9.59;
OTHER_SWIFT_FLAGS = "$(inherited) \"-D\" \"COCOAPODS\" \"-DDEBUG\"";
PRODUCT_BUNDLE_IDENTIFIER = com.audiobookshelf.app.dev;
PRODUCT_NAME = "$(TARGET_NAME)";
@@ -618,17 +739,17 @@
};
504EC3181FED79650016851F /* Release */ = {
isa = XCBuildConfiguration;
baseConfigurationReference = AF51FD2D460BCFE21FA515B2 /* Pods-App.release.xcconfig */;
baseConfigurationReference = 5F7EC3E03050241EA35154B3 /* Pods-Audiobookshelf.release.xcconfig */;
buildSettings = {
ASSETCATALOG_COMPILER_APPICON_NAME = Icons;
CLANG_ENABLE_MODULES = YES;
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 15;
CURRENT_PROJECT_VERSION = 16;
DEVELOPMENT_TEAM = 7UFJ7D8V6A;
INFOPLIST_FILE = App/Info.plist;
IPHONEOS_DEPLOYMENT_TARGET = 14.0;
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks";
MARKETING_VERSION = 0.9.58;
MARKETING_VERSION = 0.9.59;
PRODUCT_BUNDLE_IDENTIFIER = com.audiobookshelf.app;
PRODUCT_NAME = "$(TARGET_NAME)";
SWIFT_ACTIVE_COMPILATION_CONDITIONS = "";
@@ -638,6 +759,49 @@
};
name = Release;
};
E9E8814028DA5DE500D750C1 /* Debug */ = {
isa = XCBuildConfiguration;
buildSettings = {
BUNDLE_LOADER = "$(TEST_HOST)";
CLANG_CXX_LANGUAGE_STANDARD = "gnu++20";
CLANG_ENABLE_OBJC_WEAK = YES;
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 1;
GENERATE_INFOPLIST_FILE = YES;
IPHONEOS_DEPLOYMENT_TARGET = 16.0;
MARKETING_VERSION = 1.0;
MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE;
MTL_FAST_MATH = YES;
PRODUCT_BUNDLE_IDENTIFIER = com.audiobookshelf.AudiobookshelfUnitTests;
PRODUCT_NAME = "$(TARGET_NAME)";
SWIFT_EMIT_LOC_STRINGS = NO;
SWIFT_VERSION = 5.0;
TARGETED_DEVICE_FAMILY = "1,2";
TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Audiobookshelf.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/Audiobookshelf";
};
name = Debug;
};
E9E8814128DA5DE500D750C1 /* Release */ = {
isa = XCBuildConfiguration;
buildSettings = {
BUNDLE_LOADER = "$(TEST_HOST)";
CLANG_CXX_LANGUAGE_STANDARD = "gnu++20";
CLANG_ENABLE_OBJC_WEAK = YES;
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 1;
GENERATE_INFOPLIST_FILE = YES;
IPHONEOS_DEPLOYMENT_TARGET = 16.0;
MARKETING_VERSION = 1.0;
MTL_FAST_MATH = YES;
PRODUCT_BUNDLE_IDENTIFIER = com.audiobookshelf.AudiobookshelfUnitTests;
PRODUCT_NAME = "$(TARGET_NAME)";
SWIFT_EMIT_LOC_STRINGS = NO;
SWIFT_VERSION = 5.0;
TARGETED_DEVICE_FAMILY = "1,2";
TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Audiobookshelf.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/Audiobookshelf";
};
name = Release;
};
/* End XCBuildConfiguration section */
/* Begin XCConfigurationList section */
@@ -650,7 +814,7 @@
defaultConfigurationIsVisible = 0;
defaultConfigurationName = Release;
};
504EC3161FED79650016851F /* Build configuration list for PBXNativeTarget "App" */ = {
504EC3161FED79650016851F /* Build configuration list for PBXNativeTarget "Audiobookshelf" */ = {
isa = XCConfigurationList;
buildConfigurations = (
504EC3171FED79650016851F /* Debug */,
@@ -659,6 +823,15 @@
defaultConfigurationIsVisible = 0;
defaultConfigurationName = Release;
};
E9E8814228DA5DE500D750C1 /* Build configuration list for PBXNativeTarget "AudiobookshelfUnitTests" */ = {
isa = XCConfigurationList;
buildConfigurations = (
E9E8814028DA5DE500D750C1 /* Debug */,
E9E8814128DA5DE500D750C1 /* Release */,
);
defaultConfigurationIsVisible = 0;
defaultConfigurationName = Release;
};
/* End XCConfigurationList section */
};
rootObject = 504EC2FC1FED79650016851F /* Project object */;
@@ -15,8 +15,8 @@
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "504EC3031FED79650016851F"
BuildableName = "App.app"
BlueprintName = "App"
BuildableName = "Audiobookshelf.app"
BlueprintName = "Audiobookshelf"
ReferencedContainer = "container:App.xcodeproj">
</BuildableReference>
</BuildActionEntry>
@@ -28,6 +28,17 @@
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
shouldUseLaunchSchemeArgsEnv = "YES">
<Testables>
<TestableReference
skipped = "NO"
parallelizable = "YES">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "E9E8813928DA5DE500D750C1"
BuildableName = "AudiobookshelfUnitTests.xctest"
BlueprintName = "AudiobookshelfUnitTests"
ReferencedContainer = "container:App.xcodeproj">
</BuildableReference>
</TestableReference>
</Testables>
</TestAction>
<LaunchAction
@@ -45,8 +56,8 @@
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "504EC3031FED79650016851F"
BuildableName = "App.app"
BlueprintName = "App"
BuildableName = "Audiobookshelf.app"
BlueprintName = "Audiobookshelf"
ReferencedContainer = "container:App.xcodeproj">
</BuildableReference>
</BuildableProductRunnable>
@@ -62,8 +73,8 @@
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "504EC3031FED79650016851F"
BuildableName = "App.app"
BlueprintName = "App"
BuildableName = "Audiobookshelf.app"
BlueprintName = "Audiobookshelf"
ReferencedContainer = "container:App.xcodeproj">
</BuildableReference>
</BuildableProductRunnable>
+12 -10
View File
@@ -4,8 +4,10 @@ import RealmSwift
@UIApplicationMain
class AppDelegate: UIResponder, UIApplicationDelegate {
private let logger = AppLogger(category: "AppDelegate")
var window: UIWindow?
lazy var window: UIWindow? = UIWindow(frame: UIScreen.main.bounds)
var backgroundCompletionHandler: (() -> Void)?
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
@@ -13,15 +15,15 @@ class AppDelegate: UIResponder, UIApplicationDelegate {
let configuration = Realm.Configuration(
schemaVersion: 4,
migrationBlock: { migration, oldSchemaVersion in
migrationBlock: { [weak self] migration, oldSchemaVersion in
if (oldSchemaVersion < 1) {
NSLog("Realm schema version was \(oldSchemaVersion)")
self?.logger.log("Realm schema version was \(oldSchemaVersion)")
migration.enumerateObjects(ofType: DeviceSettings.className()) { oldObject, newObject in
newObject?["enableAltView"] = false
}
}
if (oldSchemaVersion < 4) {
NSLog("Realm schema version was \(oldSchemaVersion)... Reindexing server configs")
self?.logger.log("Realm schema version was \(oldSchemaVersion)... Reindexing server configs")
var indexCounter = 1
migration.enumerateObjects(ofType: ServerConnectionConfig.className()) { oldObject, newObject in
newObject?["index"] = indexCounter
@@ -43,22 +45,22 @@ class AppDelegate: UIResponder, UIApplicationDelegate {
func applicationDidEnterBackground(_ application: UIApplication) {
// Use this method to release shared resources, save user data, invalidate timers, and store enough application state information to restore your application to its current state in case it is terminated later.
// If your application supports background execution, this method is called instead of applicationWillTerminate: when the user quits.
NSLog("Audiobookself is now in the background")
logger.log("Audiobookself is now in the background")
}
func applicationWillEnterForeground(_ application: UIApplication) {
// Called as part of the transition from the background to the active state; here you can undo many of the changes made on entering the background.
NSLog("Audiobookself is now in the foreground")
logger.log("Audiobookself is now in the foreground")
}
func applicationDidBecomeActive(_ application: UIApplication) {
// Restart any tasks that were paused (or not yet started) while the application was inactive. If the application was previously in the background, optionally refresh the user interface.
NSLog("Audiobookself is now active")
logger.log("Audiobookself is now active")
}
func applicationWillTerminate(_ application: UIApplication) {
// Called when the application is about to terminate. Save data if appropriate. See also applicationDidEnterBackground:.
NSLog("Audiobookself is terminating")
logger.log("Audiobookself is terminating")
}
func application(_ app: UIApplication, open url: URL, options: [UIApplication.OpenURLOptionsKey: Any] = [:]) -> Bool {
@@ -83,10 +85,10 @@ class AppDelegate: UIResponder, UIApplicationDelegate {
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
super.touchesBegan(touches, with: event)
let statusBarRect = UIApplication.shared.statusBarFrame
let statusBarRect = self.window?.windowScene?.statusBarManager?.statusBarFrame
guard let touchPoint = event?.allTouches?.first?.location(in: self.window) else { return }
if statusBarRect.contains(touchPoint) {
if statusBarRect?.contains(touchPoint) ?? false {
NotificationCenter.default.post(name: .capacitorStatusBarTapped, object: nil)
}
}
+19 -18
View File
@@ -11,8 +11,9 @@ import RealmSwift
@objc(AbsAudioPlayer)
public class AbsAudioPlayer: CAPPlugin {
private let logger = AppLogger(category: "AbsAudioPlayer")
private var initialPlayWhenReady = false
private var isUIReady = false
override public func load() {
NotificationCenter.default.addObserver(self, selector: #selector(sendMetadata), name: NSNotification.Name(PlayerEvents.update.rawValue), object: nil)
@@ -33,20 +34,19 @@ public class AbsAudioPlayer: CAPPlugin {
}
func restorePlaybackSession() async {
// We don't need to restore if we have an active session
guard PlayerHandler.getPlaybackSession() == nil else { return }
do {
// Fetch the most recent active session
let activeSession = try await Realm().objects(PlaybackSession.self).where({
let activeSession = try Realm(queue: nil).objects(PlaybackSession.self).where({
$0.isActiveSession == true && $0.serverConnectionConfigId == Store.serverConfig?.id
}).last?.freeze()
if let activeSession = activeSession {
PlayerHandler.stopPlayback(currentSessionId: activeSession.id)
await PlayerProgress.shared.syncFromServer()
try self.startPlaybackSession(activeSession, playWhenReady: false, playbackRate: PlayerSettings.main().playbackRate)
}
} catch {
NSLog("Failed to restore playback session")
logger.error("Failed to restore playback session")
debugPrint(error)
}
}
@@ -67,7 +67,7 @@ public class AbsAudioPlayer: CAPPlugin {
let playbackRate = call.getFloat("playbackRate", 1)
if libraryItemId == nil {
NSLog("provide library item id")
logger.error("provide library item id")
return call.resolve()
}
@@ -78,7 +78,7 @@ public class AbsAudioPlayer: CAPPlugin {
let item = Database.shared.getLocalLibraryItem(localLibraryItemId: libraryItemId!)
let episode = item?.getPodcastEpisode(episodeId: episodeId)
guard let playbackSession = item?.getPlaybackSession(episode: episode) else {
NSLog("Failed to get local playback session")
logger.error("Failed to get local playback session")
return call.resolve([:])
}
@@ -87,18 +87,18 @@ public class AbsAudioPlayer: CAPPlugin {
try self.startPlaybackSession(playbackSession, playWhenReady: playWhenReady, playbackRate: playbackRate)
call.resolve(try playbackSession.asDictionary())
} catch(let exception) {
NSLog("Failed to start session")
logger.error("Failed to start session")
debugPrint(exception)
call.resolve([:])
}
} else { // Playing from the server
ApiClient.startPlaybackSession(libraryItemId: libraryItemId!, episodeId: episodeId, forceTranscode: false) { session in
ApiClient.startPlaybackSession(libraryItemId: libraryItemId!, episodeId: episodeId, forceTranscode: false) { [weak self] session in
do {
try session.save()
try self.startPlaybackSession(session, playWhenReady: playWhenReady, playbackRate: playbackRate)
try self?.startPlaybackSession(session, playWhenReady: playWhenReady, playbackRate: playbackRate)
call.resolve(try session.asDictionary())
} catch(let exception) {
NSLog("Failed to start session")
self?.logger.error("Failed to start session")
debugPrint(exception)
call.resolve([:])
}
@@ -107,7 +107,7 @@ public class AbsAudioPlayer: CAPPlugin {
}
@objc func closePlayback(_ call: CAPPluginCall) {
NSLog("Close playback")
logger.log("Close playback")
PlayerHandler.stopPlayback()
call.resolve()
@@ -193,7 +193,7 @@ public class AbsAudioPlayer: CAPPlugin {
let seconds = time / 1000
NSLog("chapter time: \(isChapterTime)")
logger.log("chapter time: \(isChapterTime)")
if isChapterTime {
PlayerHandler.setChapterSleepTime(stopAt: seconds)
return call.resolve([ "success": true ])
@@ -230,7 +230,7 @@ public class AbsAudioPlayer: CAPPlugin {
guard let localMediaProgressId = PlayerHandler.getPlaybackSession()?.localMediaProgressId else { return }
guard let localMediaProgress = Database.shared.getLocalMediaProgress(localMediaProgressId: localMediaProgressId) else { return }
guard let progressUpdate = try? localMediaProgress.asDictionary() else { return }
NSLog("Sending local progress back to the UI")
logger.log("Sending local progress back to the UI")
self.notifyListeners("onLocalMediaProgressUpdate", data: progressUpdate)
}
@@ -239,17 +239,18 @@ public class AbsAudioPlayer: CAPPlugin {
let session = PlayerHandler.getPlaybackSession()
let libraryItemId = session?.libraryItemId ?? ""
let episodeId = session?.episodeId ?? nil
NSLog("Forcing Transcode")
logger.log("Forcing Transcode")
// If direct playing then fallback to transcode
ApiClient.startPlaybackSession(libraryItemId: libraryItemId, episodeId: episodeId, forceTranscode: true) { session in
ApiClient.startPlaybackSession(libraryItemId: libraryItemId, episodeId: episodeId, forceTranscode: true) { [weak self] session in
do {
guard let self = self else { return }
try session.save()
PlayerHandler.startPlayback(sessionId: session.id, playWhenReady: self.initialPlayWhenReady, playbackRate: PlayerSettings.main().playbackRate)
self.sendPlaybackSession(session: try session.asDictionary())
self.sendMetadata()
} catch(let exception) {
NSLog("Failed to start transcoded session")
self?.logger.error("Failed to start transcoded session")
debugPrint(exception)
}
}
+8 -6
View File
@@ -28,6 +28,8 @@ extension String {
@objc(AbsDatabase)
public class AbsDatabase: CAPPlugin {
private let logger = AppLogger(category: "AbsDatabase")
@objc func setCurrentServerConnectionConfig(_ call: CAPPluginCall) {
var id = call.getString("id")
let address = call.getString("address", "")
@@ -82,7 +84,7 @@ public class AbsDatabase: CAPPlugin {
let items = Database.shared.getLocalLibraryItems()
call.resolve([ "value": try items.asDictionaryArray()])
} catch(let exception) {
NSLog("error while readling local library items")
logger.error("error while readling local library items")
debugPrint(exception)
call.resolve()
}
@@ -98,7 +100,7 @@ public class AbsDatabase: CAPPlugin {
call.resolve()
}
} catch(let exception) {
NSLog("error while readling local library items")
logger.error("error while readling local library items")
debugPrint(exception)
call.resolve()
}
@@ -114,7 +116,7 @@ public class AbsDatabase: CAPPlugin {
call.resolve()
}
} catch(let exception) {
NSLog("error while readling local library items")
logger.error("error while readling local library items")
debugPrint(exception)
call.resolve()
}
@@ -128,7 +130,7 @@ public class AbsDatabase: CAPPlugin {
do {
call.resolve([ "value": try Database.shared.getAllLocalMediaProgress().asDictionaryArray() ])
} catch {
NSLog("Error while loading local media progress")
logger.error("Error while loading local media progress")
debugPrint(error)
call.resolve(["value": []])
}
@@ -178,7 +180,7 @@ public class AbsDatabase: CAPPlugin {
return
}
NSLog("syncServerMediaProgressWithLocalMediaProgress: Saving local media progress")
logger.log("syncServerMediaProgressWithLocalMediaProgress: Saving local media progress")
try localMediaProgress.updateFromServerMediaProgress(serverMediaProgress)
call.resolve(try localMediaProgress.asDictionary())
@@ -194,7 +196,7 @@ public class AbsDatabase: CAPPlugin {
let localMediaProgressId = call.getString("localMediaProgressId")
let isFinished = call.getBool("isFinished", false)
NSLog("updateLocalMediaProgressFinished \(localMediaProgressId ?? "Unknown") | Is Finished: \(isFinished)")
logger.log("updateLocalMediaProgressFinished \(localMediaProgressId ?? "Unknown") | Is Finished: \(isFinished)")
do {
let localMediaProgress = try LocalMediaProgress.fetchOrCreateLocalMediaProgress(localMediaProgressId: localMediaProgressId, localLibraryItemId: localLibraryItemId, localEpisodeId: localEpisodeId)
+21 -19
View File
@@ -14,6 +14,8 @@ public class AbsDownloader: CAPPlugin, URLSessionDownloadDelegate {
static private let downloadsDirectory = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask)[0]
private let logger = AppLogger(category: "AbsDownloader")
private lazy var session: URLSession = {
let config = URLSessionConfiguration.background(withIdentifier: "AbsDownloader")
let queue = OperationQueue()
@@ -94,7 +96,7 @@ public class AbsDownloader: CAPPlugin, URLSessionDownloadDelegate {
private func handleDownloadTaskUpdate(downloadTask: URLSessionTask, progressHandler: DownloadProgressHandler) {
do {
guard let downloadItemPartId = downloadTask.taskDescription else { throw LibraryItemDownloadError.noTaskDescription }
NSLog("Received download update for \(downloadItemPartId)")
logger.log("Received download update for \(downloadItemPartId)")
// Find the download item
let downloadItem = Database.shared.getDownloadItem(downloadItemPartId: downloadItemPartId)
@@ -108,7 +110,7 @@ public class AbsDownloader: CAPPlugin, URLSessionDownloadDelegate {
do {
try progressHandler(downloadItem, part)
} catch {
NSLog("Error while processing progress")
logger.error("Error while processing progress")
debugPrint(error)
}
@@ -119,7 +121,7 @@ public class AbsDownloader: CAPPlugin, URLSessionDownloadDelegate {
}
self.notifyDownloadProgress()
} catch {
NSLog("DownloadItemError")
logger.error("DownloadItemError")
debugPrint(error)
}
}
@@ -127,18 +129,18 @@ public class AbsDownloader: CAPPlugin, URLSessionDownloadDelegate {
// We want to handle updating the UI in the background and throttled so we don't overload the UI with progress updates
private func notifyDownloadProgress() {
if self.monitoringProgressTimer?.isValid ?? false {
NSLog("Already monitoring progress, no need to start timer again")
logger.log("Already monitoring progress, no need to start timer again")
} else {
DispatchQueue.runOnMainQueue {
self.monitoringProgressTimer = Timer.scheduledTimer(withTimeInterval: 0.2, repeats: true, block: { t in
NSLog("Starting monitoring download progress...")
self.monitoringProgressTimer = Timer.scheduledTimer(withTimeInterval: 0.2, repeats: true, block: { [unowned self] t in
self.logger.log("Starting monitoring download progress...")
// Fetch active downloads in a thread-safe way
func fetchActiveDownloads() -> [String: DownloadItem]? {
self.progressStatusQueue.sync {
let activeDownloads = self.downloadItemProgress
if activeDownloads.isEmpty {
NSLog("Finishing monitoring download progress...")
logger.log("Finishing monitoring download progress...")
t.invalidate()
}
return activeDownloads
@@ -173,8 +175,8 @@ public class AbsDownloader: CAPPlugin, URLSessionDownloadDelegate {
statusNotification["libraryItemId"] = downloadItem.id
if ( downloadItem.didDownloadSuccessfully() ) {
ApiClient.getLibraryItemWithProgress(libraryItemId: downloadItem.libraryItemId!, episodeId: downloadItem.episodeId) { libraryItem in
guard let libraryItem = libraryItem else { NSLog("LibraryItem not found"); return }
ApiClient.getLibraryItemWithProgress(libraryItemId: downloadItem.libraryItemId!, episodeId: downloadItem.episodeId) { [weak self] libraryItem in
guard let libraryItem = libraryItem else { self?.logger.error("LibraryItem not found"); return }
let localDirectory = libraryItem.id
var coverFile: String?
@@ -206,7 +208,7 @@ public class AbsDownloader: CAPPlugin, URLSessionDownloadDelegate {
statusNotification["localMediaProgress"] = try? localMediaProgress.asDictionary()
}
self.notifyListeners("onItemDownloadComplete", data: statusNotification)
self?.notifyListeners("onItemDownloadComplete", data: statusNotification)
}
} else {
self.notifyListeners("onItemDownloadComplete", data: statusNotification)
@@ -221,22 +223,22 @@ public class AbsDownloader: CAPPlugin, URLSessionDownloadDelegate {
var episodeId = call.getString("episodeId")
if ( episodeId == "null" ) { episodeId = nil }
NSLog("Download library item \(libraryItemId ?? "N/A") / episode \(episodeId ?? "N/A")")
logger.log("Download library item \(libraryItemId ?? "N/A") / episode \(episodeId ?? "N/A")")
guard let libraryItemId = libraryItemId else { return call.resolve(["error": "libraryItemId not specified"]) }
ApiClient.getLibraryItemWithProgress(libraryItemId: libraryItemId, episodeId: episodeId) { libraryItem in
ApiClient.getLibraryItemWithProgress(libraryItemId: libraryItemId, episodeId: episodeId) { [weak self] libraryItem in
if let libraryItem = libraryItem {
NSLog("Got library item from server \(libraryItem.id)")
self?.logger.log("Got library item from server \(libraryItem.id)")
do {
if let episodeId = episodeId {
// Download a podcast episode
guard libraryItem.mediaType == "podcast" else { throw LibraryItemDownloadError.libraryItemNotPodcast }
let episode = libraryItem.media?.episodes.enumerated().first(where: { $1.id == episodeId })?.element
guard let episode = episode else { throw LibraryItemDownloadError.podcastEpisodeNotFound }
try self.startLibraryItemDownload(libraryItem, episode: episode)
try self?.startLibraryItemDownload(libraryItem, episode: episode)
} else {
// Download a book
try self.startLibraryItemDownload(libraryItem)
try self?.startLibraryItemDownload(libraryItem)
}
call.resolve()
} catch {
@@ -298,7 +300,7 @@ public class AbsDownloader: CAPPlugin, URLSessionDownloadDelegate {
}
private func startLibraryItemTrackDownload(item: LibraryItem, position: Int, track: AudioTrack, episode: PodcastEpisode?) throws -> DownloadItemPartTask {
NSLog("TRACK \(track.contentUrl!)")
logger.log("TRACK \(track.contentUrl!)")
// If we don't name metadata, then we can't proceed
guard let filename = track.metadata?.filename else {
@@ -342,10 +344,10 @@ public class AbsDownloader: CAPPlugin, URLSessionDownloadDelegate {
private func createLibraryItemFileDirectory(item: LibraryItem) throws -> String {
let itemDirectory = item.id
NSLog("ITEM DIR \(itemDirectory)")
logger.log("ITEM DIR \(itemDirectory)")
guard AbsDownloader.itemDownloadFolder(path: itemDirectory) != nil else {
NSLog("Failed to CREATE LI DIRECTORY \(itemDirectory)")
logger.error("Failed to CREATE LI DIRECTORY \(itemDirectory)")
throw LibraryItemDownloadError.failedDirectory
}
@@ -367,7 +369,7 @@ public class AbsDownloader: CAPPlugin, URLSessionDownloadDelegate {
return itemFolder
} catch {
NSLog("Failed to CREATE LI DIRECTORY \(error)")
AppLogger().error("Failed to CREATE LI DIRECTORY \(error)")
return nil
}
}
+13 -11
View File
@@ -10,10 +10,12 @@ import Capacitor
@objc(AbsFileSystem)
public class AbsFileSystem: CAPPlugin {
private let logger = AppLogger(category: "AbsFileSystem")
@objc func selectFolder(_ call: CAPPluginCall) {
let mediaType = call.getString("mediaType")
NSLog("Select Folder for media type \(mediaType ?? "UNSET")")
logger.log("Select Folder for media type \(mediaType ?? "UNSET")")
call.unavailable("Not available on iOS")
}
@@ -21,7 +23,7 @@ public class AbsFileSystem: CAPPlugin {
@objc func checkFolderPermission(_ call: CAPPluginCall) {
let folderUrl = call.getString("folderUrl")
NSLog("checkFolderPermission for folder \(folderUrl ?? "UNSET")")
logger.log("checkFolderPermission for folder \(folderUrl ?? "UNSET")")
call.unavailable("Not available on iOS")
}
@@ -30,7 +32,7 @@ public class AbsFileSystem: CAPPlugin {
let folderId = call.getString("folderId")
let forceAudioProbe = call.getBool("forceAudioProbe", false)
NSLog("scanFolder \(folderId ?? "UNSET") | Force Probe = \(forceAudioProbe)")
logger.log("scanFolder \(folderId ?? "UNSET") | Force Probe = \(forceAudioProbe)")
call.unavailable("Not available on iOS")
}
@@ -38,7 +40,7 @@ public class AbsFileSystem: CAPPlugin {
@objc func removeFolder(_ call: CAPPluginCall) {
let folderId = call.getString("folderId")
NSLog("removeFolder \(folderId ?? "UNSET")")
logger.log("removeFolder \(folderId ?? "UNSET")")
call.unavailable("Not available on iOS")
}
@@ -46,7 +48,7 @@ public class AbsFileSystem: CAPPlugin {
@objc func removeLocalLibraryItem(_ call: CAPPluginCall) {
let localLibraryItemId = call.getString("localLibraryItemId")
NSLog("removeLocalLibraryItem \(localLibraryItemId ?? "UNSET")")
logger.log("removeLocalLibraryItem \(localLibraryItemId ?? "UNSET")")
call.unavailable("Not available on iOS")
}
@@ -55,7 +57,7 @@ public class AbsFileSystem: CAPPlugin {
let localLibraryItemId = call.getString("localLibraryItemId")
let forceAudioProbe = call.getBool("forceAudioProbe", false)
NSLog("scanLocalLibraryItem \(localLibraryItemId ?? "UNSET") | Force Probe = \(forceAudioProbe)")
logger.log("scanLocalLibraryItem \(localLibraryItemId ?? "UNSET") | Force Probe = \(forceAudioProbe)")
call.unavailable("Not available on iOS")
}
@@ -64,7 +66,7 @@ public class AbsFileSystem: CAPPlugin {
let localLibraryItemId = call.getString("id")
let contentUrl = call.getString("contentUrl")
NSLog("deleteItem \(localLibraryItemId ?? "UNSET") url \(contentUrl ?? "UNSET")")
logger.log("deleteItem \(localLibraryItemId ?? "UNSET") url \(contentUrl ?? "UNSET")")
var success = false
do {
@@ -74,7 +76,7 @@ public class AbsFileSystem: CAPPlugin {
success = true
}
} catch {
NSLog("Failed to delete \(error)")
logger.error("Failed to delete \(error)")
success = false
}
@@ -85,7 +87,7 @@ public class AbsFileSystem: CAPPlugin {
let localLibraryItemId = call.getString("id")
let trackLocalFileId = call.getString("trackLocalFileId")
NSLog("deleteTrackFromItem \(localLibraryItemId ?? "UNSET") track file \(trackLocalFileId ?? "UNSET")")
logger.log("deleteTrackFromItem \(localLibraryItemId ?? "UNSET") track file \(trackLocalFileId ?? "UNSET")")
var success = false
if let localLibraryItemId = localLibraryItemId, let trackLocalFileId = trackLocalFileId, let item = Database.shared.getLocalLibraryItem(localLibraryItemId: localLibraryItemId) {
@@ -105,12 +107,12 @@ public class AbsFileSystem: CAPPlugin {
success = true
}
} catch {
NSLog("Failed to delete \(error)")
logger.error("Failed to delete \(error)")
success = false
}
}
} catch {
NSLog("Failed to delete \(error)")
logger.error("Failed to delete \(error)")
success = false
}
}
@@ -0,0 +1,44 @@
//
// PlayerTimeUtilsTests.swift
// AudiobookshelfUnitTests
//
// Created by Ron Heft on 9/20/22.
//
import XCTest
@testable import Audiobookshelf
final class PlayerTimeUtilsTests: XCTestCase {
func testCalcSeekBackTime() {
let currentTime: Double = 1000
let threeSecondsAgo = Date(timeIntervalSinceNow: -3)
let lastPlayedMs = threeSecondsAgo.timeIntervalSince1970 * 1000
XCTAssertEqual(PlayerTimeUtils.calcSeekBackTime(currentTime: currentTime, lastPlayedMs: lastPlayedMs), 998)
}
func testCalcSeekBackTimeWithZeroCurrentTime() {
let currentTime: Double = 0
let threeHundredSecondsAgo = Date(timeIntervalSinceNow: -300)
let lastPlayedMs = threeHundredSecondsAgo.timeIntervalSince1970 * 1000
XCTAssertEqual(PlayerTimeUtils.calcSeekBackTime(currentTime: currentTime, lastPlayedMs: lastPlayedMs), 0)
}
func testTimeSinceLastPlayed() throws {
let fiveSecondsAgo = Date(timeIntervalSinceNow: -5)
let lastPlayedMs = fiveSecondsAgo.timeIntervalSince1970 * 1000
XCTAssertEqual(PlayerTimeUtils.timeSinceLastPlayed(lastPlayedMs)!, -5, accuracy: 1.0)
XCTAssertNil(PlayerTimeUtils.timeSinceLastPlayed(nil))
}
func testTimeToSeekBackForSinceLastPlayed() throws {
XCTAssertEqual(PlayerTimeUtils.timeToSeekBackForSinceLastPlayed(nil), 5, "Seeks back 5 seconds for nil")
XCTAssertEqual(PlayerTimeUtils.timeToSeekBackForSinceLastPlayed(5), 2, "Seeks back 2 seconds for less than 6 seconds")
XCTAssertEqual(PlayerTimeUtils.timeToSeekBackForSinceLastPlayed(11), 10, "Seeks back 10 seconds for less than 12 seconds")
XCTAssertEqual(PlayerTimeUtils.timeToSeekBackForSinceLastPlayed(29), 15, "Seeks back 15 seconds for less than 30 seconds")
XCTAssertEqual(PlayerTimeUtils.timeToSeekBackForSinceLastPlayed(179), 20, "Seeks back 20 seconds for less than 2 minutes")
XCTAssertEqual(PlayerTimeUtils.timeToSeekBackForSinceLastPlayed(3599), 25, "Seeks back 25 seconds for less than 59 minutes")
XCTAssertEqual(PlayerTimeUtils.timeToSeekBackForSinceLastPlayed(60000), 29, "Seeks back 29 seconds for anything over 59 minuts")
}
}
+1 -1
View File
@@ -17,7 +17,7 @@ def capacitor_pods
pod 'CapacitorStorage', :path => '../../node_modules/@capacitor/storage'
end
target 'App' do
target 'Audiobookshelf' do
capacitor_pods
# Add your Pods here
@@ -39,14 +39,18 @@ class LocalLibraryItem: Object, Codable {
}
}
var coverContentUrl: String? {
var coverUrl: URL? {
if let path = self._coverContentUrl {
return AbsDownloader.itemDownloadFolder(path: path)!.absoluteString
return AbsDownloader.itemDownloadFolder(path: path)
} else {
return nil
}
}
var coverContentUrl: String? {
return self.coverUrl?.absoluteString
}
var isBook: Bool { self.mediaType == "book" }
var isPodcast: Bool { self.mediaType == "podcast" }
+77 -84
View File
@@ -19,6 +19,7 @@ enum PlayMethod:Int {
class AudioPlayer: NSObject {
internal let queue = DispatchQueue(label: "ABSAudioPlayerQueue")
internal let logger = AppLogger(category: "AudioPlayer")
// enums and @objc are not compatible
@objc dynamic var status: Int
@@ -68,7 +69,7 @@ class AudioPlayer: NSObject {
let playbackSession = self.getPlaybackSession()
guard let playbackSession = playbackSession else {
NSLog("Failed to fetch playback session. Player will not initialize")
logger.error("Failed to fetch playback session. Player will not initialize")
NotificationCenter.default.post(name: NSNotification.Name(PlayerEvents.failed.rawValue), object: nil)
return
}
@@ -86,10 +87,10 @@ class AudioPlayer: NSObject {
}
self.currentTrackIndex = getItemIndexForTime(time: playbackSession.currentTime)
NSLog("Starting track index \(self.currentTrackIndex) for start time \(playbackSession.currentTime)")
logger.log("Starting track index \(self.currentTrackIndex) for start time \(playbackSession.currentTime)")
let playerItems = self.allPlayerItems[self.currentTrackIndex..<self.allPlayerItems.count]
NSLog("Setting player items \(playerItems.count)")
logger.log("Setting player items \(playerItems.count)")
for item in Array(playerItems) {
self.audioPlayer.insert(item, after:self.audioPlayer.items().last)
@@ -99,13 +100,10 @@ class AudioPlayer: NSObject {
setupQueueObserver()
setupQueueItemStatusObserver()
NSLog("Audioplayer ready")
logger.log("Audioplayer ready")
}
deinit {
self.stopPausedTimer()
self.removeSleepTimer()
self.removeTimeObserver()
self.queueObserver?.invalidate()
self.queueItemStatusObserver?.invalidate()
}
@@ -120,8 +118,8 @@ class AudioPlayer: NSObject {
do {
try AVAudioSession.sharedInstance().setActive(false)
} catch {
NSLog("Failed to set AVAudioSession inactive")
print(error)
logger.error("Failed to set AVAudioSession inactive")
logger.error(error)
}
self.removeAudioSessionNotifications()
@@ -132,8 +130,13 @@ class AudioPlayer: NSObject {
// Remove observers
self.audioPlayer.removeObserver(self, forKeyPath: #keyPath(AVPlayer.rate), context: &playerContext)
self.audioPlayer.removeObserver(self, forKeyPath: #keyPath(AVPlayer.currentItem), context: &playerContext)
self.removeTimeObserver()
NotificationCenter.default.post(name: NSNotification.Name(PlayerEvents.closed.rawValue), object: nil)
// Remove timers
self.stopPausedTimer()
self.removeSleepTimer()
}
public func isInitialized() -> Bool {
@@ -178,6 +181,7 @@ class AudioPlayer: NSObject {
let time = CMTime(seconds: Double(seconds), preferredTimescale: timeScale)
self.timeObserverToken = self.audioPlayer.addPeriodicTimeObserver(forInterval: time, queue: self.queue) { [weak self] time in
guard let self = self else { return }
guard self.isInitialized() else { return }
guard let currentTime = self.getCurrentTime() else { return }
let isPlaying = self.isPlaying()
@@ -190,11 +194,6 @@ class AudioPlayer: NSObject {
if self.isSleepTimerSet() {
// Update the UI
NotificationCenter.default.post(name: NSNotification.Name(PlayerEvents.sleepSet.rawValue), object: nil)
// Handle a sitation where the user skips past the chapter end
if self.isChapterSleepTimerBeforeTime(currentTime) {
self.removeSleepTimer()
}
}
}
}
@@ -214,14 +213,14 @@ class AudioPlayer: NSObject {
self.audioPlayer.currentItem.map { item in
self.currentTrackIndex = self.allPlayerItems.firstIndex(of:item) ?? 0
if (self.currentTrackIndex != prevTrackIndex) {
NSLog("New Current track index \(self.currentTrackIndex)")
self.logger.log("New Current track index \(self.currentTrackIndex)")
}
}
}
}
private func setupQueueItemStatusObserver() {
NSLog("queueStatusObserver: Setting up")
logger.log("queueStatusObserver: Setting up")
// Listen for player item updates
self.queueItemStatusObserver?.invalidate()
@@ -236,13 +235,13 @@ class AudioPlayer: NSObject {
}
private func handleQueueItemStatus(playerItem: AVPlayerItem) {
NSLog("queueStatusObserver: Current item status changed")
logger.log("queueStatusObserver: Current item status changed")
guard let playbackSession = self.getPlaybackSession() else {
NotificationCenter.default.post(name: NSNotification.Name(PlayerEvents.failed.rawValue), object: nil)
return
}
if (playerItem.status == .readyToPlay) {
NSLog("queueStatusObserver: Current Item Ready to play. PlayWhenReady: \(self.playWhenReady)")
logger.log("queueStatusObserver: Current Item Ready to play. PlayWhenReady: \(self.playWhenReady)")
// Seek the player before initializing, so a currentTime of 0 does not appear in MediaProgress / session
let firstReady = self.status < 0
@@ -252,16 +251,16 @@ class AudioPlayer: NSObject {
self.seek(playbackSession.currentTime, from: "queueItemStatusObserver")
}
// Mark the player as ready
self.status = 0
// Start the player, if requested
if self.playWhenReady {
self.playWhenReady = false
self.play()
self.play(allowSeekBack: false, isInitializing: true)
} else {
// Mark the player as ready
self.status = 0
}
} else if (playerItem.status == .failed) {
NSLog("queueStatusObserver: FAILED \(playerItem.error?.localizedDescription ?? "")")
logger.error("queueStatusObserver: FAILED \(playerItem.error?.localizedDescription ?? "")")
NotificationCenter.default.post(name: NSNotification.Name(PlayerEvents.failed.rawValue), object: nil)
}
}
@@ -269,8 +268,8 @@ class AudioPlayer: NSObject {
private func startPausedTimer() {
guard self.pausedTimer == nil else { return }
self.queue.async {
self.pausedTimer = Timer.scheduledTimer(withTimeInterval: 10, repeats: true) { timer in
NSLog("PAUSE TIMER: Syncing from server")
self.pausedTimer = Timer.scheduledTimer(withTimeInterval: 10, repeats: true) { [weak self] timer in
self?.logger.log("PAUSE TIMER: Syncing from server")
Task { await PlayerProgress.shared.syncFromServer() }
}
}
@@ -282,16 +281,15 @@ class AudioPlayer: NSObject {
}
// MARK: - Methods
public func play(allowSeekBack: Bool = false) {
guard self.isInitialized() else { return }
public func play(allowSeekBack: Bool = false, isInitializing: Bool = false) {
guard self.isInitialized() || isInitializing else { return }
guard let session = self.getPlaybackSession() else {
NotificationCenter.default.post(name: NSNotification.Name(PlayerEvents.failed.rawValue), object: nil)
return
}
// Determine where we are starting playback
let lastPlayed = (session.updatedAt ?? 0)/1000
let currentTime = allowSeekBack ? calculateSeekBackTimeAtCurrentTime(session.currentTime, lastPlayed: lastPlayed) : session.currentTime
let currentTime = allowSeekBack ? PlayerTimeUtils.calcSeekBackTime(currentTime: session.currentTime, lastPlayedMs: session.updatedAt) : session.currentTime
// Sync our new playback position
Task { await PlayerProgress.shared.syncFromPlayer(currentTime: currentTime, includesPlayProgress: self.isPlaying(), isStopping: false) }
@@ -299,57 +297,42 @@ class AudioPlayer: NSObject {
// Start playback, with a seek, for as smooth a scrub bar start as possible
let currentTrackStartOffset = session.audioTracks[self.currentTrackIndex].startOffset ?? 0.0
let seekTime = currentTime - currentTrackStartOffset
self.audioPlayer.seek(to: CMTime(seconds: seekTime, preferredTimescale: 1000), toleranceBefore: .zero, toleranceAfter: .zero) { [weak self] completed in
guard completed else { return }
self?.resumePlayback()
}
}
private func calculateSeekBackTimeAtCurrentTime(_ currentTime: Double, lastPlayed: Double) -> Double {
let difference = Date.timeIntervalSinceReferenceDate - lastPlayed
var time: Double = 0
// Scale seek back time based on how long since last play
if lastPlayed == 0 {
time = 5
} else if difference < 6 {
time = 2
} else if difference < 12 {
time = 10
} else if difference < 30 {
time = 15
} else if difference < 180 {
time = 20
} else if difference < 3600 {
time = 25
} else {
time = 29
DispatchQueue.runOnMainQueue {
self.audioPlayer.seek(to: CMTime(seconds: seekTime, preferredTimescale: 1000), toleranceBefore: .zero, toleranceAfter: .zero) { [weak self] completed in
guard completed else { return }
self?.resumePlayback()
}
}
// Wind the clock back
return currentTime - time
}
private func resumePlayback() {
NSLog("PLAY: Resuming playback")
logger.log("PLAY: Resuming playback")
// Stop the paused timer
self.stopPausedTimer()
self.markAudioSessionAs(active: true)
self.audioPlayer.play()
self.audioPlayer.rate = self.tmpRate
DispatchQueue.runOnMainQueue {
self.audioPlayer.play()
self.audioPlayer.rate = self.tmpRate
}
self.status = 1
// Update the progress
self.updateNowPlaying()
// Handle a chapter sleep timer that may now be invalid
self.handleTrackChangeForChapterSleepTimer()
}
public func pause() {
guard self.isInitialized() else { return }
NSLog("PAUSE: Pausing playback")
self.audioPlayer.pause()
logger.log("PAUSE: Pausing playback")
DispatchQueue.runOnMainQueue {
self.audioPlayer.pause()
}
self.markAudioSessionAs(active: false)
Task {
@@ -370,17 +353,17 @@ class AudioPlayer: NSObject {
self.pause()
NSLog("SEEK: Seek to \(to) from \(from)")
logger.log("SEEK: Seek to \(to) from \(from) and continuePlaying(\(continuePlaying)")
guard let playbackSession = self.getPlaybackSession() else { return }
let currentTrack = playbackSession.audioTracks[self.currentTrackIndex]
let ctso = currentTrack.startOffset ?? 0.0
let trackEnd = ctso + currentTrack.duration
NSLog("SEEK: Seek current track END = \(trackEnd)")
logger.log("SEEK: Seek current track END = \(trackEnd)")
let indexOfSeek = getItemIndexForTime(time: to)
NSLog("SEEK: Seek to index \(indexOfSeek) | Current index \(self.currentTrackIndex)")
logger.log("SEEK: Seek to index \(indexOfSeek) | Current index \(self.currentTrackIndex)")
// Reconstruct queue if seeking to a different track
if (self.currentTrackIndex != indexOfSeek) {
@@ -394,26 +377,34 @@ class AudioPlayer: NSObject {
self.status = -1
let playerItems = self.allPlayerItems[indexOfSeek..<self.allPlayerItems.count]
self.audioPlayer.removeAllItems()
for item in Array(playerItems) {
self.audioPlayer.insert(item, after:self.audioPlayer.items().last)
DispatchQueue.runOnMainQueue {
self.audioPlayer.removeAllItems()
for item in Array(playerItems) {
self.audioPlayer.insert(item, after:self.audioPlayer.items().last)
}
}
setupQueueItemStatusObserver()
} else {
NSLog("SEEK: Seeking in current item \(to)")
logger.log("SEEK: Seeking in current item \(to)")
let currentTrackStartOffset = playbackSession.audioTracks[self.currentTrackIndex].startOffset ?? 0.0
let seekTime = to - currentTrackStartOffset
self.audioPlayer.seek(to: CMTime(seconds: seekTime, preferredTimescale: 1000)) { [weak self] completed in
guard completed else { return NSLog("SEEK: WARNING: seeking not completed (to \(seekTime)") }
guard let self = self else { return }
if continuePlaying {
self.resumePlayback()
DispatchQueue.runOnMainQueue {
self.audioPlayer.seek(to: CMTime(seconds: seekTime, preferredTimescale: 1000)) { [weak self] completed in
self?.logger.log("SEEK: Completion handler called and continuePlaying(\(continuePlaying)")
guard completed else {
self?.logger.log("SEEK: WARNING: seeking not completed (to \(seekTime)")
return
}
guard let self = self else { return }
if continuePlaying {
self.resumePlayback()
}
self.updateNowPlaying()
}
self.updateNowPlaying()
}
}
}
@@ -422,8 +413,10 @@ class AudioPlayer: NSObject {
let playbackSpeedChanged = rate > 0.0 && rate != self.tmpRate && !(observed && rate == 1)
if self.audioPlayer.rate != rate {
NSLog("setPlaybakRate rate changed from \(self.audioPlayer.rate) to \(rate)")
self.audioPlayer.rate = rate
logger.log("setPlaybakRate rate changed from \(self.audioPlayer.rate) to \(rate)")
DispatchQueue.runOnMainQueue {
self.audioPlayer.rate = rate
}
}
self.rate = rate
@@ -477,7 +470,7 @@ class AudioPlayer: NSObject {
} else if (playbackSession.playMethod == PlayMethod.local.rawValue) {
guard let localFile = track.getLocalFile() else {
// Worst case we can stream the file
NSLog("Unable to play local file. Resulting to streaming \(track.localFileId ?? "Unknown")")
logger.log("Unable to play local file. Resulting to streaming \(track.localFileId ?? "Unknown")")
let filename = track.metadata?.filename ?? ""
let filenameEncoded = filename.addingPercentEncoding(withAllowedCharacters: NSCharacterSet.urlQueryAllowed)
let urlstr = "\(Store.serverConfig!.address)/s/item/\(itemId)/\(filenameEncoded ?? "")?token=\(Store.serverConfig!.token)"
@@ -497,8 +490,8 @@ class AudioPlayer: NSObject {
do {
try AVAudioSession.sharedInstance().setCategory(.playback, mode: .spokenAudio)
} catch {
NSLog("Failed to set AVAudioSession category")
print(error)
logger.error("Failed to set AVAudioSession category")
logger.error(error)
}
}
@@ -506,7 +499,7 @@ class AudioPlayer: NSObject {
do {
try AVAudioSession.sharedInstance().setActive(active)
} catch {
NSLog("Failed to set audio session as active=\(active)")
logger.error("Failed to set audio session as active=\(active)")
}
}
@@ -637,11 +630,11 @@ class AudioPlayer: NSObject {
public override func observeValue(forKeyPath keyPath: String?, of object: Any?, change: [NSKeyValueChangeKey : Any]?, context: UnsafeMutableRawPointer?) {
if context == &playerContext {
if keyPath == #keyPath(AVPlayer.rate) {
NSLog("playerContext observer player rate")
logger.log("playerContext observer player rate")
self.setPlaybackRate(change?[.newKey] as? Float ?? 1.0, observed: true)
} else if keyPath == #keyPath(AVPlayer.currentItem) {
NotificationCenter.default.post(name: NSNotification.Name(PlayerEvents.update.rawValue), object: nil)
NSLog("WARNING: Item ended")
logger.log("WARNING: Item ended")
}
} else {
super.observeValue(forKeyPath: keyPath, of: object, change: change, context: context)
@@ -31,7 +31,7 @@ extension AudioPlayer {
}
public func setSleepTimer(secondsUntilSleep: Double) {
NSLog("SLEEP TIMER: Sleeping in \(secondsUntilSleep) seconds")
logger.log("SLEEP TIMER: Sleeping in \(secondsUntilSleep) seconds")
self.removeSleepTimer()
self.sleepTimeRemaining = secondsUntilSleep
@@ -47,9 +47,13 @@ extension AudioPlayer {
NotificationCenter.default.post(name: NSNotification.Name(PlayerEvents.sleepSet.rawValue), object: nil)
}
public func setChapterSleepTimer(stopAt: Double) {
NSLog("SLEEP TIMER: Scheduling for chapter end \(stopAt)")
public func setChapterSleepTimer(stopAt: Double?) {
self.removeSleepTimer()
guard let stopAt = stopAt else { return }
guard let currentTime = self.getCurrentTime() else { return }
guard stopAt >= currentTime else { return }
logger.log("SLEEP TIMER: Scheduling for chapter end \(stopAt)")
// Schedule the observation time
self.sleepTimeChapterStopAt = stopAt
@@ -111,7 +115,12 @@ extension AudioPlayer {
// MARK: - Internal helpers
internal func decrementSleepTimerIfRunning() {
internal func handleTrackChangeForChapterSleepTimer() {
// If no sleep timer is set, this does nothing
self.setChapterSleepTimer(stopAt: self.sleepTimeChapterStopAt)
}
private func decrementSleepTimerIfRunning() {
if var sleepTimeRemaining = self.sleepTimeRemaining {
sleepTimeRemaining -= 1
self.sleepTimeRemaining = sleepTimeRemaining
@@ -124,7 +133,7 @@ extension AudioPlayer {
}
private func handleSleepEnd() {
NSLog("SLEEP TIMER: Pausing audio")
logger.log("SLEEP TIMER: Pausing audio")
self.pause()
self.removeSleepTimer()
}
@@ -137,19 +146,11 @@ extension AudioPlayer {
self.sleepTimeChapterStopAt = nil
}
internal func isChapterSleepTimerBeforeTime(_ time: Double) -> Bool {
if let chapterStopAt = self.sleepTimeChapterStopAt {
return chapterStopAt <= time
}
return false
}
internal func isCountdownSleepTimerSet() -> Bool {
private func isCountdownSleepTimerSet() -> Bool {
return self.sleepTimeRemaining != nil
}
internal func isChapterSleepTimerSet() -> Bool {
private func isChapterSleepTimerSet() -> Bool {
return self.sleepTimeChapterStopAt != nil
}
+11 -13
View File
@@ -15,31 +15,24 @@ class PlayerHandler {
guard let session = Database.shared.getPlaybackSession(id: sessionId) else { return }
// Clean up the existing player
if player != nil {
player?.destroy()
player = nil
}
resetPlayer()
// Cleanup and sync old sessions
cleanupOldSessions(currentSessionId: sessionId)
Task { await PlayerProgress.shared.syncToServer() }
// Set now playing info
NowPlayingInfo.shared.setSessionMetadata(metadata: NowPlayingMetadata(id: session.id, itemId: session.libraryItemId!, artworkUrl: session.coverPath, title: session.displayTitle ?? "Unknown title", author: session.displayAuthor, series: nil))
NowPlayingInfo.shared.setSessionMetadata(metadata: NowPlayingMetadata(id: session.id, itemId: session.libraryItemId!, title: session.displayTitle ?? "Unknown title", author: session.displayAuthor, series: nil))
// Create the audio player
player = AudioPlayer(sessionId: sessionId, playWhenReady: playWhenReady, playbackRate: playbackRate)
}
public static func stopPlayback() {
public static func stopPlayback(currentSessionId: String? = nil) {
// Pause playback first, so we can sync our current progress
player?.pause()
player?.destroy()
player = nil
cleanupOldSessions(currentSessionId: nil)
resetPlayer()
cleanupOldSessions(currentSessionId: currentSessionId)
NowPlayingInfo.shared.reset()
}
@@ -52,7 +45,7 @@ class PlayerHandler {
if paused {
self.player?.pause()
} else {
self.player?.play()
self.player?.play(allowSeekBack: true)
}
}
}
@@ -157,4 +150,9 @@ class PlayerHandler {
debugPrint(error)
}
}
private static func resetPlayer() {
player?.destroy()
player = nil
}
}
+19 -17
View File
@@ -14,6 +14,8 @@ class PlayerProgress {
private static let TIME_BETWEEN_SESSION_SYNC_IN_SECONDS = 10.0
private let logger = AppLogger(category: "PlayerProgress")
private init() {}
@@ -28,8 +30,8 @@ class PlayerProgress {
try await updateServerSessionFromLocalSession(session, rateLimitSync: !isStopping)
}
} catch {
debugPrint("Failed to syncFromPlayer")
debugPrint(error)
logger.error("Failed to syncFromPlayer")
logger.error(error)
}
await UIApplication.shared.endBackgroundTask(backgroundToken)
}
@@ -39,8 +41,8 @@ class PlayerProgress {
do {
try await updateAllServerSessionFromLocalSession()
} catch {
debugPrint("Failed to syncToServer")
debugPrint(error)
logger.error("Failed to syncToServer")
logger.error(error)
}
await UIApplication.shared.endBackgroundTask(backgroundToken)
}
@@ -50,8 +52,8 @@ class PlayerProgress {
do {
try await updateLocalSessionFromServerMediaProgress()
} catch {
debugPrint("Failed to syncFromServer")
debugPrint(error)
logger.error("Failed to syncFromServer")
logger.error(error)
}
await UIApplication.shared.endBackgroundTask(backgroundToken)
}
@@ -96,7 +98,7 @@ class PlayerProgress {
try localMediaProgress.updateFromPlaybackSession(session)
NSLog("Local progress saved to the database")
logger.log("Local progress saved to the database")
// Send the local progress back to front-end
NotificationCenter.default.post(name: NSNotification.Name(PlayerEvents.localProgress.rawValue), object: nil)
@@ -104,7 +106,7 @@ class PlayerProgress {
private func updateAllServerSessionFromLocalSession() async throws {
try await withThrowingTaskGroup(of: Void.self) { [self] group in
for session in try await Realm().objects(PlaybackSession.self).where({ $0.serverConnectionConfigId == Store.serverConfig?.id }) {
for session in try Realm(queue: nil).objects(PlaybackSession.self).where({ $0.serverConnectionConfigId == Store.serverConfig?.id }) {
let session = session.freeze()
group.addTask {
try await self.updateServerSessionFromLocalSession(session)
@@ -141,7 +143,7 @@ class PlayerProgress {
session = session.freeze()
guard safeToSync else { return }
NSLog("Sending sessionId(\(session.id)) to server with currentTime(\(session.currentTime))")
logger.log("Sending sessionId(\(session.id)) to server with currentTime(\(session.currentTime))")
var success = false
if session.isLocal {
@@ -161,25 +163,25 @@ class PlayerProgress {
}
private func updateLocalSessionFromServerMediaProgress() async throws {
NSLog("updateLocalSessionFromServerMediaProgress: Checking if local media progress was updated on server")
guard let session = try await Realm().objects(PlaybackSession.self).last(where: {
logger.log("updateLocalSessionFromServerMediaProgress: Checking if local media progress was updated on server")
guard let session = try Realm(queue: nil).objects(PlaybackSession.self).last(where: {
$0.isActiveSession == true && $0.serverConnectionConfigId == Store.serverConfig?.id
})?.freeze() else {
NSLog("updateLocalSessionFromServerMediaProgress: Failed to get session")
logger.log("updateLocalSessionFromServerMediaProgress: Failed to get session")
return
}
// Fetch the current progress
let progress = await ApiClient.getMediaProgress(libraryItemId: session.libraryItemId!, episodeId: session.episodeId)
guard let progress = progress else {
NSLog("updateLocalSessionFromServerMediaProgress: No progress object")
logger.log("updateLocalSessionFromServerMediaProgress: No progress object")
return
}
// Determine which session is newer
let serverLastUpdate = progress.lastUpdate
guard let localLastUpdate = session.updatedAt else {
NSLog("updateLocalSessionFromServerMediaProgress: No local session updatedAt")
logger.log("updateLocalSessionFromServerMediaProgress: No local session updatedAt")
return
}
let serverCurrentTime = progress.currentTime
@@ -190,16 +192,16 @@ class PlayerProgress {
// Update the session, if needed
if serverIsNewerThanLocal && currentTimeIsDifferent {
NSLog("updateLocalSessionFromServerMediaProgress: Server has newer time than local serverLastUpdate=\(serverLastUpdate) localLastUpdate=\(localLastUpdate)")
logger.log("updateLocalSessionFromServerMediaProgress: Server has newer time than local serverLastUpdate=\(serverLastUpdate) localLastUpdate=\(localLastUpdate)")
guard let session = session.thaw() else { return }
try session.update {
session.currentTime = serverCurrentTime
session.updatedAt = serverLastUpdate
}
NSLog("updateLocalSessionFromServerMediaProgress: Updated session currentTime newCurrentTime=\(serverCurrentTime) previousCurrentTime=\(localCurrentTime)")
logger.log("updateLocalSessionFromServerMediaProgress: Updated session currentTime newCurrentTime=\(serverCurrentTime) previousCurrentTime=\(localCurrentTime)")
PlayerHandler.seek(amount: session.currentTime)
} else {
NSLog("updateLocalSessionFromServerMediaProgress: Local session does not need updating; local has latest progress")
logger.log("updateLocalSessionFromServerMediaProgress: Local session does not need updating; local has latest progress")
}
}
@@ -0,0 +1,47 @@
//
// PlayerTimeUtils.swift
// Audiobookshelf
//
// Created by Ron Heft on 9/20/22.
//
import Foundation
class PlayerTimeUtils {
private init() {}
static func calcSeekBackTime(currentTime: TimeInterval, lastPlayedMs: Double?) -> TimeInterval {
let sinceLastPlayed = timeSinceLastPlayed(lastPlayedMs)
let timeToSeekBack = timeToSeekBackForSinceLastPlayed(sinceLastPlayed)
let currentTimeAfterSeekBack = currentTime.advanced(by: -timeToSeekBack)
return max(currentTimeAfterSeekBack, 0)
}
static internal func timeSinceLastPlayed(_ lastPlayedMs: Double?) -> TimeInterval? {
guard let lastPlayedMs = lastPlayedMs else { return nil }
let lastPlayed = Date(timeIntervalSince1970: lastPlayedMs / 1000)
return lastPlayed.timeIntervalSinceNow
}
static internal func timeToSeekBackForSinceLastPlayed(_ sinceLastPlayed: TimeInterval?) -> TimeInterval {
if let sinceLastPlayed = sinceLastPlayed {
if sinceLastPlayed < 6 {
return 2
} else if sinceLastPlayed < 12 {
return 10
} else if sinceLastPlayed < 30 {
return 15
} else if sinceLastPlayed < 180 {
return 20
} else if sinceLastPlayed < 3600 {
return 25
} else {
return 29
}
} else {
return 5
}
}
}
+17 -15
View File
@@ -9,6 +9,8 @@ import Foundation
import Alamofire
class ApiClient {
private static let logger = AppLogger(category: "ApiClient")
public static func getData(from url: URL, completion: @escaping (UIImage?) -> Void) {
URLSession.shared.dataTask(with: url, completionHandler: {(data, response, error) in
if let data = data {
@@ -19,7 +21,7 @@ class ApiClient {
public static func postResource<T: Decodable>(endpoint: String, parameters: [String: Any], decodable: T.Type = T.self, callback: ((_ param: T) -> Void)?) {
if (Store.serverConfig == nil) {
NSLog("Server config not set")
logger.error("Server config not set")
return
}
@@ -32,7 +34,7 @@ class ApiClient {
case .success(let obj):
callback?(obj)
case .failure(let error):
NSLog("api request to \(endpoint) failed")
logger.error("api request to \(endpoint) failed")
print(error)
}
}
@@ -40,7 +42,7 @@ class ApiClient {
public static func postResource<T: Encodable, U: Decodable>(endpoint: String, parameters: T, decodable: U.Type = U.self, callback: ((_ param: U) -> Void)?) {
if (Store.serverConfig == nil) {
NSLog("Server config not set")
logger.error("Server config not set")
return
}
@@ -53,7 +55,7 @@ class ApiClient {
case .success(let obj):
callback?(obj)
case .failure(let error):
NSLog("api request to \(endpoint) failed")
logger.error("api request to \(endpoint) failed")
print(error)
}
}
@@ -69,7 +71,7 @@ class ApiClient {
public static func postResource<T:Encodable>(endpoint: String, parameters: T, callback: ((_ success: Bool) -> Void)?) {
if (Store.serverConfig == nil) {
NSLog("Server config not set")
logger.error("Server config not set")
callback?(false)
return
}
@@ -83,7 +85,7 @@ class ApiClient {
case .success(_):
callback?(true)
case .failure(let error):
NSLog("api request to \(endpoint) failed")
logger.error("api request to \(endpoint) failed")
print(error)
callback?(false)
@@ -93,7 +95,7 @@ class ApiClient {
public static func patchResource<T: Encodable>(endpoint: String, parameters: T, callback: ((_ success: Bool) -> Void)?) {
if (Store.serverConfig == nil) {
NSLog("Server config not set")
logger.error("Server config not set")
callback?(false)
return
}
@@ -107,7 +109,7 @@ class ApiClient {
case .success(_):
callback?(true)
case .failure(let error):
NSLog("api request to \(endpoint) failed")
logger.error("api request to \(endpoint) failed")
print(error)
callback?(false)
}
@@ -124,7 +126,7 @@ class ApiClient {
public static func getResource<T: Decodable>(endpoint: String, decodable: T.Type = T.self, callback: ((_ param: T?) -> Void)?) {
if (Store.serverConfig == nil) {
NSLog("Server config not set")
logger.error("Server config not set")
callback?(nil)
return
}
@@ -138,7 +140,7 @@ class ApiClient {
case .success(let obj):
callback?(obj)
case .failure(let error):
NSLog("api request to \(endpoint) failed")
logger.error("api request to \(endpoint) failed")
print(error)
}
}
@@ -193,10 +195,10 @@ class ApiClient {
if ( !localMediaProgressList.isEmpty ) {
let payload = LocalMediaProgressSyncPayload(localMediaProgress: localMediaProgressList)
NSLog("Sending sync local progress request with \(localMediaProgressList.count) progress items")
logger.log("Sending sync local progress request with \(localMediaProgressList.count) progress items")
postResource(endpoint: "api/me/sync-local-progress", parameters: payload, decodable: MediaProgressSyncResponsePayload.self) { response in
let resultsPayload = LocalMediaProgressSyncResultsPayload(numLocalMediaProgressForServer: localMediaProgressList.count, numServerProgressUpdates: response.numServerProgressUpdates, numLocalProgressUpdates: response.localProgressUpdates?.count)
NSLog("Media Progress Sync | \(String(describing: try? resultsPayload.asDictionary()))")
logger.log("Media Progress Sync | \(String(describing: try? resultsPayload.asDictionary()))")
if let updates = response.localProgressUpdates {
for update in updates {
@@ -212,13 +214,13 @@ class ApiClient {
callback(resultsPayload)
}
} else {
NSLog("No local media progress to sync")
logger.log("No local media progress to sync")
callback(LocalMediaProgressSyncResultsPayload(numLocalMediaProgressForServer: 0, numServerProgressUpdates: 0, numLocalProgressUpdates: 0))
}
}
public static func updateMediaProgress<T:Encodable>(libraryItemId: String, episodeId: String?, payload: T, callback: @escaping () -> Void) {
NSLog("updateMediaProgress \(libraryItemId) \(episodeId ?? "NIL") \(payload)")
logger.log("updateMediaProgress \(libraryItemId) \(episodeId ?? "NIL") \(payload)")
let endpoint = episodeId?.isEmpty ?? true ? "api/me/progress/\(libraryItemId)" : "api/me/progress/\(libraryItemId)/\(episodeId ?? "")"
patchResource(endpoint: endpoint, parameters: payload) { success in
callback()
@@ -226,7 +228,7 @@ class ApiClient {
}
public static func getMediaProgress(libraryItemId: String, episodeId: String?) async -> MediaProgress? {
NSLog("getMediaProgress \(libraryItemId) \(episodeId ?? "NIL")")
logger.log("getMediaProgress \(libraryItemId) \(episodeId ?? "NIL")")
let endpoint = episodeId?.isEmpty ?? true ? "api/me/progress/\(libraryItemId)" : "api/me/progress/\(libraryItemId)/\(episodeId ?? "")"
return await getResource(endpoint: endpoint, decodable: MediaProgress.self)
}
+8 -5
View File
@@ -12,6 +12,8 @@ class Database {
public static var shared = {
return Database()
}()
private let logger = AppLogger(category: "Database")
private init() {}
@@ -30,7 +32,7 @@ class Database {
existing.token = config.token
}
} catch {
NSLog("failed to update server config")
logger.error("failed to update server config")
debugPrint(error)
}
@@ -51,7 +53,7 @@ class Database {
realm.add(config)
}
} catch(let exception) {
NSLog("failed to save server config")
logger.error("failed to save server config")
debugPrint(exception)
}
@@ -70,7 +72,7 @@ class Database {
}
}
} catch(let exception) {
NSLog("failed to delete server config")
logger.error("failed to delete server config")
debugPrint(exception)
}
}
@@ -101,7 +103,7 @@ class Database {
}
}
} catch(let exception) {
NSLog("failed to save server config active index")
logger.error("failed to save server config active index")
debugPrint(exception)
}
}
@@ -121,7 +123,7 @@ class Database {
realm.add(deviceSettings)
}
} catch {
NSLog("failed to save device settings")
logger.error("failed to save device settings")
}
}
@@ -242,6 +244,7 @@ class Database {
public func getPlaybackSession(id: String) -> PlaybackSession? {
do {
let realm = try Realm()
realm.refresh() // Refresh, because working with stale sessions leads to wrong times
return realm.object(ofType: PlaybackSession.self, forPrimaryKey: id)
} catch {
debugPrint(error)
+1 -1
View File
@@ -59,7 +59,7 @@ extension CAPPluginCall {
let json = try JSONSerialization.data(withJSONObject: value)
return try JSONDecoder().decode(type, from: json)
} catch {
NSLog("Failed to get json for \(key)")
AppLogger().error("Failed to get json for \(key)")
debugPrint(error)
return nil
}
+72
View File
@@ -0,0 +1,72 @@
//
// AppLogger.swift
// AppLogger
//
// Created by Fernando Fernandes on 29.06.20.
//
import Foundation
import os
/// Logs app info by using the newest Swift unified logging APIs.
///
/// Reference:
/// - [Explore logging in Swift (WWDC20)](https://developer.apple.com/wwdc20/10168)
/// - [Unified Logging](https://developer.apple.com/documentation/os/logging)
/// - [OSLog](https://developer.apple.com/documentation/os/oslog)
/// - [Logger](https://developer.apple.com/documentation/os/logger)
public struct AppLogger {
// MARK: - Properties
/// Default values used by the `AppLogger`.
public struct Defaults {
public static let subsystem = Bundle.main.bundleIdentifier ?? "ABS"
public static let category = "default"
public static let isPrivate = false
}
// MARK: - Private Properties
private let logger: Logger
// MARK: - Lifecycle
/// Creates an `AppLogger` instance.
///
/// - Parameters:
/// - subsystem: `String`. Organizes large topic areas within the app or apps. For example, you might define
/// a subsystem for each process that you create. The default is `Bundle.main.bundleIdentifier ?? "AppLogger"`.
/// - category: `String`. Within a `subsystem`, you define categories to further distinguish parts of that
/// subsystem. For example, if you used a single subsystem for your app, you might create separate categories for
/// model code and user-interface code. In a game, you might use categories to distinguish between physics, AI,
/// world simulation, and rendering. The default is `default`.
public init(subsystem: String = Defaults.subsystem, category: String = Defaults.category) {
self.logger = Logger(subsystem: subsystem, category: category)
}
}
// MARK: - Interface
public extension AppLogger {
func log(_ information: String, isPrivate: Bool = Defaults.isPrivate) {
if isPrivate {
logger.log("\(information, privacy: .private)")
} else {
logger.log("\(information, privacy: .public)")
}
}
func error(_ information: String, isPrivate: Bool = Defaults.isPrivate) {
if isPrivate {
logger.error("\(information, privacy: .private)")
} else {
logger.error("\(information, privacy: .public)")
}
}
func error(_ error: Error) {
logger.error("\(String(describing: error))")
}
}
+20 -24
View File
@@ -11,15 +11,22 @@ import MediaPlayer
struct NowPlayingMetadata {
var id: String
var itemId: String
var artworkUrl: String?
var title: String
var author: String?
var series: String?
var coverUrl: URL? {
guard let config = Store.serverConfig else { return nil }
guard let url = URL(string: "\(config.address)/api/items/\(itemId)/cover?token=\(config.token)") else { return nil }
return url
if self.isLocal {
guard let item = Database.shared.getLocalLibraryItem(byServerLibraryItemId: self.itemId) else { return nil }
return item.coverUrl
} else {
guard let config = Store.serverConfig else { return nil }
guard let url = URL(string: "\(config.address)/api/items/\(itemId)/cover?token=\(config.token)") else { return nil }
return url
}
}
var isLocal: Bool { id.starts(with: "play_local_") }
}
class NowPlayingInfo {
@@ -34,28 +41,17 @@ class NowPlayingInfo {
public func setSessionMetadata(metadata: NowPlayingMetadata) {
setMetadata(artwork: nil, metadata: metadata)
let isLocalItem = metadata.itemId.starts(with: "local_")
if isLocalItem {
guard let artworkUrl = metadata.artworkUrl else { return }
let coverImage = UIImage(contentsOfFile: artworkUrl)
guard let coverImage = coverImage else { return }
let artwork = MPMediaItemArtwork(boundsSize: coverImage.size) { _ -> UIImage in
return coverImage
guard let url = metadata.coverUrl else { return }
// For local images, "downloading" is occurring off disk, hence this code path works as expected
ApiClient.getData(from: url) { [self] image in
guard let downloadedImage = image else {
return
}
let artwork = MPMediaItemArtwork.init(boundsSize: downloadedImage.size, requestHandler: { _ -> UIImage in
return downloadedImage
})
self.setMetadata(artwork: artwork, metadata: metadata)
} else {
guard let url = metadata.coverUrl else { return }
ApiClient.getData(from: url) { [self] image in
guard let downloadedImage = image else {
return
}
let artwork = MPMediaItemArtwork.init(boundsSize: downloadedImage.size, requestHandler: { _ -> UIImage in
return downloadedImage
})
self.setMetadata(artwork: artwork, metadata: metadata)
}
}
}
public func update(duration: Double, currentTime: Double, rate: Float) {
+2 -2
View File
@@ -1,12 +1,12 @@
{
"name": "audiobookshelf-app",
"version": "0.9.58-beta",
"version": "0.9.59-beta",
"lockfileVersion": 2,
"requires": true,
"packages": {
"": {
"name": "audiobookshelf-app",
"version": "0.9.57-beta",
"version": "0.9.59-beta",
"dependencies": {
"@capacitor/android": "^3.4.3",
"@capacitor/app": "^1.1.1",
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "audiobookshelf-app",
"version": "0.9.58-beta",
"version": "0.9.59-beta",
"author": "advplyr",
"scripts": {
"dev": "nuxt --hostname 0.0.0.0 --port 1337",
+1 -1
View File
@@ -58,7 +58,7 @@ export default {
},
computed: {
bookCoverAspectRatio() {
return this.$store.getters['getBookCoverAspectRatio']
return this.$store.getters['libraries/getBookCoverAspectRatio']
},
bookItems() {
return this.collection.books || []
+1 -1
View File
@@ -199,7 +199,7 @@ export default {
return null
},
bookCoverAspectRatio() {
return this.$store.getters['getBookCoverAspectRatio']
return this.$store.getters['libraries/getBookCoverAspectRatio']
},
libraryItemId() {
return this.libraryItem.id
+1 -1
View File
@@ -68,7 +68,7 @@ export default {
return this.$store.state.libraries.currentLibraryId
},
bookCoverAspectRatio() {
return this.$store.getters['getBookCoverAspectRatio']
return this.$store.getters['libraries/getBookCoverAspectRatio']
},
totalResults() {
return this.bookResults.length + this.seriesResults.length + this.authorResults.length + this.podcastResults.length
+2 -1
View File
@@ -45,7 +45,8 @@ const Constants = {
}
export {
PlayerState
PlayerState,
BookCoverAspectRatio
}
export default ({ app }, inject) => {
Binary file not shown.
+30
View File
@@ -0,0 +1,30 @@
<?xml version="1.0" standalone="no"?>
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd" >
<svg xmlns="http://www.w3.org/2000/svg">
<metadata>Generated by IcoMoon</metadata>
<defs>
<font id="absicons" horiz-adv-x="1024">
<font-face units-per-em="1024" ascent="960" descent="-64" />
<missing-glyph horiz-adv-x="1024" />
<glyph unicode="&#x20;" horiz-adv-x="512" d="" />
<glyph unicode="&#xe900;" glyph-name="audiobookshelf" horiz-adv-x="862" d="M854.995 455.649c-5.287 4.402-13.511 10.896-24.656 18.576v86.499c0 220.513-178.763 399.276-399.281 399.276v0c-220.513 0-399.276-178.763-399.276-399.276v-86.499c-11.145-7.697-19.369-14.174-24.656-18.576-4.368-3.659-7.126-9.116-7.126-15.217 0-0.006 0-0.011 0-0.017v0.001-103.619c0-0.011 0-0.025 0-0.038 0-6.101 2.758-11.558 7.095-15.191l0.031-0.025c12.326-10.26 40.612-31.782 84.651-53.849v-10.072c0-27.162 17.525-49.186 39.139-49.186v0c21.614 0 39.139 22.023 39.139 49.186v248.534c0 27.166-17.525 49.19-39.139 49.19v0c-20.707 0-37.657-20.215-39.043-45.795l-0.096-0.048v51.221c0 187.38 151.902 339.278 339.282 339.278v0c187.38 0 339.282-151.898 339.282-339.278v-51.221l-0.096 0.048c-1.386 25.58-18.332 45.795-39.043 45.795v0c-21.614 0-39.135-22.023-39.135-49.19v-248.512c0-27.162 17.521-49.186 39.135-49.186v0c21.618 0 39.139 22.023 39.139 49.186v10.072c44.042 22.067 72.329 43.585 84.655 53.849 4.368 3.661 7.126 9.12 7.126 15.223 0 0.011 0 0.022 0 0.034v-0.002 103.597c0 0.003 0 0.007 0 0.010 0 6.103-2.758 11.561-7.095 15.198l-0.031 0.025zM291.835 23.619c26.11 0 47.276 21.166 47.276 47.276v0 529.382c0 0.001 0 0.003 0 0.004 0 26.11-21.166 47.276-47.276 47.276h-48.846c-26.112 0-47.281-21.168-47.281-47.281v0-529.382c0-26.11 21.166-47.276 47.276-47.276 0.002 0 0.003 0 0.005 0v0zM213.535 520.595h107.764v-11.258h-107.764zM455.484 23.619c26.11 0 47.276 21.166 47.276 47.276v0 529.382c0 0.001 0 0.003 0 0.004 0 26.11-21.166 47.276-47.276 47.276h-48.846c-26.112 0-47.281-21.168-47.281-47.281v0-529.382c0-26.11 21.166-47.276 47.276-47.276 0.002 0 0.003 0 0.005 0v0zM377.183 520.595h107.75v-11.258h-107.75zM619.132 23.619c26.11 0 47.276 21.166 47.276 47.276v0 529.382c0 0.001 0 0.003 0 0.004 0 26.11-21.166 47.276-47.276 47.276h-48.846c-26.112 0-47.281-21.168-47.281-47.281v0-529.382c0-26.11 21.166-47.276 47.276-47.276 0.002 0 0.003 0 0.005 0v0zM540.831 520.595h107.75v-11.258h-107.75zM114.101 7.257h633.92c19.676 0 35.626-15.95 35.626-35.626v-0.004c0-19.676-15.95-35.626-35.626-35.626h-633.92c-19.676 0-35.626 15.95-35.626 35.626v0.004c0 19.676 15.95 35.626 35.626 35.626z" />
<glyph unicode="&#xe901;" glyph-name="microphone-2" horiz-adv-x="713" d="M713.333 484.083c0 26.75-21.667 48.333-48.333 48.333-26.75 0-48.333-21.667-48.333-48.333 0-95.25-22.833-173.917-69.083-226.417-41.917-47.5-105.25-74.917-190.833-74.917s-148.917 27.333-190.833 74.833c-46.333 52.5-69.083 131.167-69.083 226.5 0 26.75-21.667 48.333-48.333 48.333-26.75 0-48.333-21.667-48.333-48.333 0-118.75 30.75-219.417 93.25-290.333 46-52.167 108-87.5 186.083-101.25v-79.333h-118.5c-21.167 0-38.5-17.333-38.5-38.5v-38.583h468.667v38.583c0 21.167-17.333 38.5-38.5 38.5h-118.75v79.333c78.083 13.833 140 49.083 186.083 101.333 62.5 70.833 93.333 171.5 93.333 290.25v0 0zM356.667 960c98.667 0 179.333-80.75 179.333-179.333v-4.667h-96.417v-114.25h96.5v-58.167h-96.5v-114.25h96.5v-41.083c0-98.667-80.75-179.333-179.333-179.333-98.667 0-179.333 80.75-179.333 179.333v41.083h96.5v114.25h-96.583v58.167h96.5v114.25h-96.5v4.667c0 98.583 80.667 179.333 179.333 179.333v0z" />
<glyph unicode="&#xe902;" glyph-name="microphone-1" horiz-adv-x="768" d="M192 768v-320c0-106 86-192 192-192s192 86 192 192h-160c-17.6 0-32 14.4-32 32s14.4 32 32 32h160v64h-160c-17.6 0-32 14.4-32 32s14.4 32 32 32h160v64h-160c-17.6 0-32 14.4-32 32s14.4 32 32 32h160c0 106-86 192-192 192s-192-86-192-192zM640 480v-32c0-141.4-114.6-256-256-256s-256 114.6-256 256v80c0 26.6-21.4 48-48 48s-48-21.4-48-48v-80c0-178.2 132.4-325.4 304-348.8v-67.2h-96c-26.6 0-48-21.4-48-48s21.4-48 48-48h288c26.6 0 48 21.4 48 48s-21.4 48-48 48h-96v67.2c171.6 23.4 304 170.6 304 348.8v80c0 26.6-21.4 48-48 48s-48-21.4-48-48v-48z" />
<glyph unicode="&#xe903;" glyph-name="radio" d="M989.6 866c25.4 7.4 40 34.2 32.6 59.6s-34.2 40-59.8 32.4l-859-251.8c-18.8-5.4-35.8-14.6-50.2-26.4-32.2-23.2-53.2-61-53.2-103.8v-512c0-70.6 57.4-128 128-128h768c70.6 0 128 57.4 128 128v512c0 70.6-57.4 128-128 128h-459l552.6 162zM736 160c-88.4 0-160 71.6-160 160s71.6 160 160 160 160-71.6 160-160-71.6-160-160-160zM160 448c0 17.6 14.4 32 32 32h192c17.6 0 32-14.4 32-32s-14.4-32-32-32h-192c-17.6 0-32 14.4-32 32zM128 320c0 17.6 14.4 32 32 32h256c17.6 0 32-14.4 32-32s-14.4-32-32-32h-256c-17.6 0-32 14.4-32 32zM160 192c0 17.6 14.4 32 32 32h192c17.6 0 32-14.4 32-32s-14.4-32-32-32h-192c-17.6 0-32 14.4-32 32z" />
<glyph unicode="&#xe904;" glyph-name="podcast" horiz-adv-x="1043" d="M585.253 366.417v-430.408h-127.13v430.408c-41.76 22.603-70.128 66.8-70.128 117.637 0 73.822 59.863 133.685 133.693 133.685 73.822 0 133.693-59.863 133.693-133.685 0-50.837-28.369-95.033-70.128-117.637v0zM333.872 865.196c24.76 12.21 34.907 42.193 22.697 66.927-12.21 24.743-42.167 34.907-66.91 22.705-101.045-49.843-179.63-132.301-229.287-229.397-36.682-71.708-57.596-151.499-60.117-232.021-2.539-81.201 13.475-163.226 50.718-238.72 45.861-93.004 123.614-175.725 238.084-234.288 24.531-12.541 54.59-2.802 67.123 21.729 12.541 24.531 2.811 54.581-21.72 67.114-93.717 47.933-156.968 114.843-193.879 189.642-29.77 60.381-42.575 126.153-40.537 191.382 2.063 65.866 19.156 131.12 49.164 189.76 40.18 78.552 103.49 145.139 184.666 185.167v0zM753.718 954.837c-24.743 12.21-54.709 2.038-66.919-22.705s-2.055-54.726 22.705-66.927c81.184-40.036 144.494-106.615 184.657-185.175 29.999-58.631 47.1-123.886 49.155-189.76 2.038-65.229-10.767-131.002-40.528-191.382-36.911-74.799-100.162-141.709-193.879-189.642-24.531-12.541-34.253-42.592-21.72-67.114 12.533-24.531 42.592-34.262 67.123-21.729 114.461 58.555 192.223 141.284 238.084 234.288 37.242 75.495 53.257 157.52 50.718 238.72-2.522 80.522-23.436 160.313-60.109 232.021-49.665 97.096-128.242 179.554-229.287 229.406v0zM702.992 734.942c-18.638 13.425-44.63 9.187-58.054-9.451-13.416-18.638-9.187-44.63 9.451-58.054 7.515-5.409 14.885-11.48 22.111-18.12 42.21-38.915 67.241-90.804 72.498-145.708 5.315-55.396-9.434-114.096-46.854-166.019-6.351-8.822-13.671-17.764-21.95-26.747-15.581-16.914-14.503-43.288 2.437-58.861 16.931-15.573 43.296-14.477 58.869 2.437 10.020 10.86 19.411 22.425 28.148 34.576 49.775 69.11 69.339 147.746 62.172 222.443-7.201 75.172-41.36 146.116-98.905 199.177-9.052 8.347-19.029 16.464-29.923 24.327v0zM388.98 667.412c18.638 13.425 22.875 39.416 9.451 58.054s-39.416 22.875-58.054 9.459c-10.903-7.863-20.871-15.98-29.923-24.319-57.553-53.070-91.713-124.005-98.914-199.177-7.167-74.688 12.397-153.35 62.181-222.46 8.754-12.142 18.137-23.699 28.148-34.559 15.573-16.914 41.938-18.010 58.861-2.437 16.948 15.573 18.027 41.938 2.445 58.861-8.279 8.992-15.598 17.925-21.95 26.747-37.412 51.923-52.17 110.614-46.854 166.011 5.256 54.895 30.288 106.793 72.498 145.708 7.234 6.632 14.605 12.703 22.111 18.112v0z" />
<glyph unicode="&#xe905;" glyph-name="books-1" d="M384 832v-640h128v640h-128zM512 746.667l170.667-554.667 128 42.667-170.667 554.667-128-42.667zM213.333 746.667v-554.667h128v554.667h-128zM128 149.333v-85.333h768v85.333h-768z" />
<glyph unicode="&#xe906;" glyph-name="database-2" horiz-adv-x="876" d="M437.75 960c240.583 0 435.583-91.333 435.583-203.833 0-112.583-195.083-203.833-435.583-203.833s-435.583 91.333-435.583 203.833c0 112.5 195.083 203.833 435.583 203.833v0zM2.167 278.083v-156.5c77.5-275.25 843.167-222.083 871.25 14.083v156.333c-38.25-259.25-810-277.917-871.25-13.917v0 0zM0 685.5v-152.833c77.5-268.833 847.417-232.5 875.583-1.917v152.75c-38.333-253.25-814.333-255.833-875.583 2v0zM0 488v-156.5c77.5-275.25 847.417-238.083 875.583-1.917v156.333c-38.333-259.25-814.333-261.917-875.583 2.083v0z" />
<glyph unicode="&#xe910;" glyph-name="headphones" d="M288 384h-64v-448h64c17.6 0 32 14.4 32 32v384c0 17.6-14.4 32-32 32zM736 384c-17.602 0-32-14.4-32-32v-384c0-17.6 14.398-32 32-32h64v448h-64zM1024 448c0 282.77-229.23 512-512 512s-512-229.23-512-512c0-61.412 10.83-120.29 30.656-174.848-19.478-33.206-30.656-71.87-30.656-113.152 0-112.846 83.448-206.188 192-221.716v443.418c-31.914-4.566-61.664-15.842-87.754-32.378-5.392 26.718-8.246 54.364-8.246 82.676 0 229.75 186.25 416 416 416s416-186.25 416-416c0-28.314-2.83-55.968-8.22-82.696-26.1 16.546-55.854 27.848-87.78 32.418v-443.44c108.548 15.532 192 108.874 192 221.714 0 41.274-11.178 79.934-30.648 113.138 19.828 54.566 30.648 113.452 30.648 174.866z" />
<glyph unicode="&#xe911;" glyph-name="music" d="M960 960h64v-736c0-88.366-100.29-160-224-160s-224 71.634-224 160c0 88.368 100.29 160 224 160 62.684 0 119.342-18.4 160-48.040v368.040l-512-113.778v-494.222c0-88.366-100.288-160-224-160s-224 71.634-224 160c0 88.368 100.288 160 224 160 62.684 0 119.342-18.4 160-48.040v624.040l576 128z" />
<glyph unicode="&#xe914;" glyph-name="video" d="M384 672c0 88.366 71.634 160 160 160s160-71.634 160-160c0-88.366-71.634-160-160-160s-160 71.634-160 160zM0 672c0 88.366 71.634 160 160 160s160-71.634 160-160c0-88.366-71.634-160-160-160s-160 71.634-160 160zM768 352v96c0 35.2-28.8 64-64 64h-640c-35.2 0-64-28.8-64-64v-320c0-35.2 28.8-64 64-64h640c35.2 0 64 28.8 64 64v96l256-160v448l-256-160zM640 192h-512v192h512v-192z" />
<glyph unicode="&#xe91e;" glyph-name="microphone-3" d="M480 256c88.366 0 160 71.634 160 160v384c0 88.366-71.634 160-160 160s-160-71.634-160-160v-384c0-88.366 71.636-160 160-160zM704 512v-96c0-123.71-100.29-224-224-224-123.712 0-224 100.29-224 224v96h-64v-96c0-148.238 112.004-270.3 256-286.22v-129.78h-128v-64h320v64h-128v129.78c143.994 15.92 256 137.982 256 286.22v96h-64z" />
<glyph unicode="&#xe91f;" glyph-name="book" d="M896 832v-832h-672c-53.026 0-96 42.98-96 96s42.974 96 96 96h608v768h-640c-70.398 0-128-57.6-128-128v-768c0-70.4 57.602-128 128-128h768v896h-64zM224.056 128v0c-0.018-0.002-0.038 0-0.056 0-17.672 0-32-14.326-32-32s14.328-32 32-32c0.018 0 0.038 0.002 0.056 0.002v-0.002h607.89v64h-607.89z" />
<glyph unicode="&#xe920;" glyph-name="books-2" horiz-adv-x="1152" d="M224 832h-192c-17.6 0-32-14.4-32-32v-704c0-17.6 14.4-32 32-32h192c17.6 0 32 14.4 32 32v704c0 17.6-14.4 32-32 32zM192 640h-128v64h128v-64zM544 832h-192c-17.6 0-32-14.4-32-32v-704c0-17.6 14.4-32 32-32h192c17.6 0 32 14.4 32 32v704c0 17.6-14.4 32-32 32zM512 640h-128v64h128v-64zM765.088 782.52l-171.464-86.394c-15.716-7.918-22.096-27.258-14.178-42.976l287.978-571.548c7.918-15.718 27.258-22.098 42.976-14.178l171.464 86.392c15.716 7.92 22.096 27.26 14.178 42.974l-287.978 571.55c-7.92 15.718-27.26 22.1-42.976 14.18z" />
<glyph unicode="&#xe927;" glyph-name="file-picture" d="M832 64h-640v128l192 320 263-320 185 128v-256zM832 480c0-53.020-42.98-96-96-96-53.022 0-96 42.98-96 96s42.978 96 96 96c53.020 0 96-42.98 96-96zM917.806 730.924c-22.212 30.292-53.174 65.7-87.178 99.704s-69.412 64.964-99.704 87.178c-51.574 37.82-76.592 42.194-90.924 42.194h-496c-44.112 0-80-35.888-80-80v-864c0-44.112 35.888-80 80-80h736c44.112 0 80 35.888 80 80v624c0 14.332-4.372 39.35-42.194 90.924zM785.374 785.374c30.7-30.7 54.8-58.398 72.58-81.374h-153.954v153.946c22.984-17.78 50.678-41.878 81.374-72.572zM896 16c0-8.672-7.328-16-16-16h-736c-8.672 0-16 7.328-16 16v864c0 8.672 7.328 16 16 16 0 0 495.956 0.002 496 0v-224c0-17.672 14.326-32 32-32h224v-624z" />
<glyph unicode="&#xe964;" glyph-name="database-1" d="M512 960c-282.77 0-512-71.634-512-160v-128c0-88.366 229.23-160 512-160s512 71.634 512 160v128c0 88.366-229.23 160-512 160zM512 416c-282.77 0-512 71.634-512 160v-192c0-88.366 229.23-160 512-160s512 71.634 512 160v192c0-88.366-229.23-160-512-160zM512 128c-282.77 0-512 71.634-512 160v-192c0-88.366 229.23-160 512-160s512 71.634 512 160v192c0-88.366-229.23-160-512-160z" />
<glyph unicode="&#xe9a5;" glyph-name="rocket" d="M704 896l-320-320h-192l-192-256c0 0 203.416 56.652 322.066 30.084l-322.066-414.084 421.902 328.144c58.838-134.654-37.902-328.144-37.902-328.144l256 192v192l320 320 64 320-320-64z" />
<glyph unicode="&#xe9b5;" glyph-name="power" d="M384 960l-384-512h384l-256-512 896 640h-512l384 384z" />
<glyph unicode="&#xe9d9;" glyph-name="star" d="M1024 562.95l-353.78 51.408-158.22 320.582-158.216-320.582-353.784-51.408 256-249.538-60.432-352.352 316.432 166.358 316.432-166.358-60.434 352.352 256.002 249.538z" />
<glyph unicode="&#xe9da;" glyph-name="heart" d="M755.188 896c-107.63 0-200.258-87.554-243.164-179-42.938 91.444-135.578 179-243.216 179-148.382 0-268.808-120.44-268.808-268.832 0-301.846 304.5-380.994 512.022-679.418 196.154 296.576 511.978 387.206 511.978 679.418 0 148.392-120.43 268.832-268.812 268.832z" />
<glyph unicode="&#xea9b;" glyph-name="rss" d="M136.294 209.070c-75.196 0-136.292-61.334-136.292-136.076 0-75.154 61.1-135.802 136.292-135.802 75.466 0 136.494 60.648 136.494 135.802-0.002 74.742-61.024 136.076-136.494 136.076zM0.156 612.070v-196.258c127.784 0 247.958-49.972 338.458-140.512 90.384-90.318 140.282-211.036 140.282-339.3h197.122c-0.002 372.82-303.282 676.070-675.862 676.070zM0.388 960v-196.356c455.782 0 826.756-371.334 826.756-827.644h196.856c0 564.47-459.254 1024-1023.612 1024z" />
</font></defs></svg>

After

Width:  |  Height:  |  Size: 13 KiB

Binary file not shown.
Binary file not shown.
+2 -1
View File
@@ -32,7 +32,8 @@ export const state = () => ({
icon: 'replay_30',
value: 30
}
]
],
libraryIcons: ['database', 'audiobookshelf', 'books-1', 'books-2', 'book-1', 'microphone-1', 'microphone-3', 'radio', 'podcast', 'rss', 'headphones', 'music', 'file-picture', 'rocket', 'power', 'star', 'heart']
})
export const getters = {
-4
View File
@@ -36,10 +36,6 @@ export const getters = {
if (!state.serverSettings) return null
return state.serverSettings[key]
},
getBookCoverAspectRatio: state => {
if (!state.serverSettings) return 1
return state.serverSettings.coverAspectRatio === 0 ? 1.6 : 1
},
getJumpForwardTime: state => {
if (!state.deviceData || !state.deviceData.deviceSettings) return 10
return state.deviceData.deviceSettings.jumpForwardTime || 10
+10
View File
@@ -1,3 +1,5 @@
const { BookCoverAspectRatio } = require('../plugins/constants')
export const state = () => ({
libraries: [],
lastLoad: 0,
@@ -18,6 +20,14 @@ export const getters = {
getCurrentLibraryMediaType: (state, getters) => {
var currLib = getters.getCurrentLibrary
return currLib ? currLib.mediaType : null
},
getCurrentLibrarySettings: (state, getters) => {
if (!getters.getCurrentLibrary) return null
return getters.getCurrentLibrary.settings
},
getBookCoverAspectRatio: (state, getters) => {
if (!getters.getCurrentLibrarySettings || isNaN(getters.getCurrentLibrarySettings.coverAspectRatio)) return 1
return getters.getCurrentLibrarySettings.coverAspectRatio === BookCoverAspectRatio.STANDARD ? 1.6 : 1
}
}
+2 -1
View File
@@ -4,7 +4,8 @@ module.exports = {
purge: {
options: {
safelist: [
'bg-success'
'bg-success',
'bg-info'
]
}
},