With the tech-focused audience of ABS, I anticipate that percentage is even higher
Supported on all iOS devices from iPhone 6 / iPhone 5s and above
Adds log statements to iOS background lifecycle hooks, to assist with development and tracking down syncing issues
Adds helper extensions for working with JSON and serializing to dictionary objects
Adds decoder logic for JSON where fields may be returned as String or Int/Double
This is not an issue for API endpoints on Java with Jackson, as that is transparently handled
Realm
Converts the existing DataClasses to Realm objects, to allow for persisting into the database as needed
Manually implements Codeable on Realm objects, as Realm List is not Codeable
Refactors object models into a model-per-file structure vs. having all classes in one file
Adds Realm Extensions like update() and delete() to make working with Realm objects easier and eliminate the need for DAO pattern, which Realm is not designed around
Bumped the schema to version 2 from 1 to accommodate the new models required for offline functionality
Refactors Realm usage to not use a shared Realm instance locked to a specific Thread
Addresses multi-threading issues by use frozen() objects and ThreadSafeReferences
AbsAudioPlayer
Adds a new Capacitor method onReady() to fire when UI is fully loaded
This is the hook to restore a previously active listening session
Refactores the logic of starting a playback session to find common code paths between online / offline playback
Persists the playbackSpeed in Realm and references it when needed
This is to enable proper restoration of the last playback speed when opening the app fresh, as well as support CarPlay restoring in the future, when the JavaScript front-end would not be loaded
Implements logic to sync local playback sessions to the UI
Prevents metadata from being sent to the UI when the AudioPlayer is not fully loaded
Guards against incorrect progress, playback state, or playback speed from appearing in UI, and in some cases, syncing back the native code
AbsDatabase
Implements Capacitor methods present on Android to persist local media progress
Implements Capacitor methods present on Android to sync local media progress with the server
Implements local library item methods
AbsFileSystem
Marks methods that will never be implemented on iOS as unavailable
Implements endpoints to sync progress with the server
Begins the work of using async/await syntax for some endpoints
Improves using Encodable/Decodable objects in API endpoints
PlayerProgress
Implements syncing between sessions, MediaProgress, and server objects
Uses the iOS background APIs to ensure sync operations always occur, even if iOS is in the process of removing the app from memory
This also lays the groundwork for syncing with CarPlay and improving sync actions when the iOS app is completing running in the background with the front-end unloaded
PlayerHandler
Ensures bad data is not passed around by verifying the AudioPlayer is initialized before returning data
Implements syncing progress with the server when the player is paused
Persists playback sessions to the database for restoration
Marks old playback sessions as inactive when starting a new session
Native iOS AudioPlayer
Loads downloaded iOS assets
Will fallback to streaming from server if file is corrupt
Implements logic for displaying downloaded files in the Now Playing views on iOS
Address some playback session threading issues, with the playback session persisted in the database
Fixes a progress syncing issue, where the playback progress could get synced at currentTime=0 with a race condition when initializing the AudioPlayer
AbsDownloader
Handles downloading of library items and tracks
Sends download progress to the user interface
Implements downloading in a way that will allow us to leverage iOS background downloading in the future
For example, for podcast episodes or letting a slow download run in the background when the app is exited
Current task list of remaining items:
Fix logging out of app
Fix incorrect progress object sent to local sync endpoint
Fix download progress never completing
Cascade deletes and prevent orphaned records in the database
Remove Unrealm and merge native Realm branch into current downloads branch
Test offline artwork display in Now Playing widget
Test downloading an audiobook without artwork
Test downloading a podcast without artwork
Persist the playback session and restore on app start
Determine how to start a playback session offline
Determine how to sync a playback session to the server if it started offline
Will the front-end respond to this through events, or is work required on the front-end?
Restore playback session on startup
Implement PlayerNotificationService.checkCurrentSessionProgress from Android
Implement session sync on server connection
Fix audio starting to play when restoring session on launch
Lifecycle hook for when webapp is started (trigger session restore)
🔄 This issue represents a GitHub Pull Request. It cannot be merged through Gitea due to API limitations.
## 📋 Pull Request Information
**Original PR:** https://github.com/advplyr/audiobookshelf-app/pull/277
**Author:** [@ronaldheft](https://github.com/ronaldheft)
**Created:** 7/10/2022
**Status:** ✅ Merged
**Merged:** 8/21/2022
**Merged by:** [@advplyr](https://github.com/advplyr)
**Base:** `master` ← **Head:** `ios-downloads`
---
### 📝 Commits (10+)
- [`2ca9ce7`](https://github.com/advplyr/audiobookshelf-app/commit/2ca9ce797d3cccee92fd4c7154e5741b0de9f9bc) feat: iOS download groundwork
- [`16ca746`](https://github.com/advplyr/audiobookshelf-app/commit/16ca7460f4b96b0bb9ad2178ea34b49f0e25edac) Convert back to Swift objects
- [`affcdea`](https://github.com/advplyr/audiobookshelf-app/commit/affcdea09bfa9e31203e3a487f2dd83c15947583) Fix class name collisions
- [`f40da56`](https://github.com/advplyr/audiobookshelf-app/commit/f40da562a6013a719a9efabb190439469b434190) Fix missing braces
- [`29f08a5`](https://github.com/advplyr/audiobookshelf-app/commit/29f08a5e5de357e88cb19e06b8e9145be6be091e) Fix use of optionals
- [`a29a766`](https://github.com/advplyr/audiobookshelf-app/commit/a29a7669e1c47170f990b37896512c6f214e9c5f) Add unrealm to use Swift object in db
- [`f56561b`](https://github.com/advplyr/audiobookshelf-app/commit/f56561b8984e727fb4b20fc9ca9f8b84a3f45570) Refactor into smaller functions
- [`e620f53`](https://github.com/advplyr/audiobookshelf-app/commit/e620f537058a223b7cacb3d7bb700cae79b1a294) Get something saving in the database
- [`175e642`](https://github.com/advplyr/audiobookshelf-app/commit/175e64208130e30f03acce147adb10b6742620ce) First pass at attempting to save library items
- [`b7725c4`](https://github.com/advplyr/audiobookshelf-app/commit/b7725c455bb76262db3f51e607a3ee7b1f504933) Prototype response to app
### 📊 Changes
**51 files changed** (+3200 additions, -509 deletions)
<details>
<summary>View changed files</summary>
📝 `components/app/AudioPlayerContainer.vue` (+20 -0)
📝 `components/tables/podcast/EpisodeRow.vue` (+15 -3)
📝 `ios/App/App.xcodeproj/project.pbxproj` (+122 -6)
➕ `ios/App/App.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist` (+8 -0)
📝 `ios/App/App/AppDelegate.swift` (+5 -1)
📝 `ios/App/App/plugins/AbsAudioPlayer.m` (+2 -0)
📝 `ios/App/App/plugins/AbsAudioPlayer.swift` (+87 -23)
📝 `ios/App/App/plugins/AbsDatabase.m` (+4 -1)
📝 `ios/App/App/plugins/AbsDatabase.swift` (+140 -6)
📝 `ios/App/App/plugins/AbsDownloader.swift` (+364 -47)
📝 `ios/App/App/plugins/AbsFileSystem.swift` (+49 -23)
➖ `ios/App/Shared/models/DataClasses.swift` (+0 -141)
📝 `ios/App/Shared/models/DeviceSettings.swift` (+11 -18)
➕ `ios/App/Shared/models/PlaybackMetadata.swift` (+14 -0)
📝 `ios/App/Shared/models/PlaybackSession.swift` (+150 -22)
➕ `ios/App/Shared/models/PlayerSettings.swift` (+30 -0)
➕ `ios/App/Shared/models/PlayerState.swift` (+15 -0)
📝 `ios/App/Shared/models/ServerConnectionConfig.swift` (+18 -20)
➕ `ios/App/Shared/models/download/DownloadItem.swift` (+97 -0)
➕ `ios/App/Shared/models/download/DownloadItemPart.swift` (+95 -0)
_...and 31 more files_
</details>
### 📄 Description
# iOS Downloading / Improved Progress Syncing
Supports iOS downloads and improved progress syncing. Resolve #178. Fixes #319.
## User Interface
- Adds lifecycle events to the front-end to provide better hooks for the native code
- Event `abs-ui-ready` when UI is fully mounted
- `AbsAudioPlayer.onReady()` when the audio player is fully loaded and all settings have been initialized
- Enables the download button for audiobooks on iOS
- Enables the download button for podcasts on iOS
- Disables reordering downloaded tracks on iOS
- Removes the "Folder" location on downloaded files for iOS, as it does not apply
- Removes Scan and Force Re-scan options on iOS
- Send iOS users to the bookshelf view after deleting a file vs. the folders view
- Rephrases the delete confirmation box to make sense for both iOS and Android
## iOS General
- Bumps the minimum iOS version to iOS 14 from iOS 12 to enable use of `async`/`await` Swift functionality
- [92.7% of iOS users are on iOS 14 or above](https://iosref.com/ios-usage)
- With the tech-focused audience of ABS, I anticipate that percentage is even higher
- Supported on all iOS devices from iPhone 6 / iPhone 5s and above
- Adds log statements to iOS background lifecycle hooks, to assist with development and tracking down syncing issues
- Adds helper extensions for working with JSON and serializing to dictionary objects
- Adds decoder logic for JSON where fields may be returned as String or Int/Double
- This is not an issue for API endpoints on Java with Jackson, as that is transparently handled
## Realm
- Converts the existing DataClasses to Realm objects, to allow for persisting into the database as needed
- Manually implements `Codeable` on Realm objects, as Realm `List` is not `Codeable`
- Refactors object models into a model-per-file structure vs. having all classes in one file
- Adds Realm Extensions like `update()` and `delete()` to make working with Realm objects easier and eliminate the need for DAO pattern, which Realm is not designed around
- Bumped the schema to version `2` from `1` to accommodate the new models required for offline functionality
- Refactors Realm usage to not use a shared Realm instance locked to a specific Thread
- [This is a bad practice for Realm](https://stackoverflow.com/a/31594548), and the recommended usage is to create a new Realm instance as needed
- [Realm internally caches instances per thread and handles this logic for us](https://www.mongodb.com/docs/realm-legacy/docs/swift/3.17.1/api/Classes/Realm.html)
- Addresses multi-threading issues by use `frozen()` objects and `ThreadSafeReferences`
## AbsAudioPlayer
- Adds a new Capacitor method `onReady()` to fire when UI is fully loaded
- This is the hook to restore a previously active listening session
- Refactores the logic of starting a playback session to find common code paths between online / offline playback
- Persists the `playbackSpeed` in Realm and references it when needed
- This is to enable proper restoration of the last playback speed when opening the app fresh, as well as support CarPlay restoring in the future, when the JavaScript front-end would not be loaded
- Implements logic to sync local playback sessions to the UI
- Prevents metadata from being sent to the UI when the AudioPlayer is not fully loaded
- Guards against incorrect progress, playback state, or playback speed from appearing in UI, and in some cases, syncing back the native code
## AbsDatabase
- Implements Capacitor methods present on Android to persist local media progress
- Implements Capacitor methods present on Android to sync local media progress with the server
- Implements local library item methods
## AbsFileSystem
- Marks methods that will never be implemented on iOS as unavailable
- Implements deleting library items / podcast episodes
## ApiClient
- Implements endpoints to sync progress with the server
- Begins the work of using `async`/`await` syntax for some endpoints
- Improves using `Encodable`/`Decodable` objects in API endpoints
## PlayerProgress
- Implements syncing between sessions, `MediaProgress`, and server objects
- Uses the iOS background APIs to ensure sync operations always occur, even if iOS is in the process of removing the app from memory
- This also lays the groundwork for syncing with CarPlay and improving sync actions when the iOS app is completing running in the background with the front-end unloaded
## PlayerHandler
- Ensures bad data is not passed around by verifying the AudioPlayer is initialized before returning data
- Implements syncing progress with the server when the player is paused
- Persists playback sessions to the database for restoration
- Marks old playback sessions as inactive when starting a new session
## Native iOS AudioPlayer
- Loads downloaded iOS assets
- Will fallback to streaming from server if file is corrupt
- Implements logic for displaying downloaded files in the Now Playing views on iOS
- Address some playback session threading issues, with the playback session persisted in the database
- Fixes a progress syncing issue, where the playback progress could get synced at `currentTime=0` with a race condition when initializing the AudioPlayer
## AbsDownloader
- Handles downloading of library items and tracks
- Sends download progress to the user interface
- Implements downloading in a way that will allow us to leverage iOS background downloading in the future
- For example, for podcast episodes or letting a slow download run in the background when the app is exited
---
Current task list of remaining items:
- [x] Fix logging out of app
- [x] Fix incorrect progress object sent to local sync endpoint
- [x] Fix download progress never completing
- [x] Cascade deletes and prevent orphaned records in the database
- [x] Remove Unrealm and merge native Realm branch into current downloads branch
- [x] Test offline artwork display in Now Playing widget
- [x] Test downloading an audiobook without artwork
- [x] Test downloading a podcast without artwork
- [x] Persist the playback session and restore on app start
- [x] Determine how to start a playback session offline
- [x] Determine how to sync a playback session to the server if it started offline
- [x] Will the front-end respond to this through events, or is work required on the front-end?
- [x] Restore playback session on startup
- [x] Implement `PlayerNotificationService.checkCurrentSessionProgress` from Android
- [x] Implement session sync on server connection
- [x] Fix audio starting to play when restoring session on launch
- [x] Lifecycle hook for when webapp is started (trigger session restore)
---
<sub>🔄 This issue represents a GitHub Pull Request. It cannot be merged through Gitea due to API limitations.</sub>
Blocking a user prevents them from interacting with repositories, such as opening or commenting on pull requests or issues. Learn more about blocking a user.
📋 Pull Request Information
Original PR: https://github.com/advplyr/audiobookshelf-app/pull/277
Author: @ronaldheft
Created: 7/10/2022
Status: ✅ Merged
Merged: 8/21/2022
Merged by: @advplyr
Base:
master← Head:ios-downloads📝 Commits (10+)
2ca9ce7feat: iOS download groundwork16ca746Convert back to Swift objectsaffcdeaFix class name collisionsf40da56Fix missing braces29f08a5Fix use of optionalsa29a766Add unrealm to use Swift object in dbf56561bRefactor into smaller functionse620f53Get something saving in the database175e642First pass at attempting to save library itemsb7725c4Prototype response to app📊 Changes
51 files changed (+3200 additions, -509 deletions)
View changed files
📝
components/app/AudioPlayerContainer.vue(+20 -0)📝
components/tables/podcast/EpisodeRow.vue(+15 -3)📝
ios/App/App.xcodeproj/project.pbxproj(+122 -6)➕
ios/App/App.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist(+8 -0)📝
ios/App/App/AppDelegate.swift(+5 -1)📝
ios/App/App/plugins/AbsAudioPlayer.m(+2 -0)📝
ios/App/App/plugins/AbsAudioPlayer.swift(+87 -23)📝
ios/App/App/plugins/AbsDatabase.m(+4 -1)📝
ios/App/App/plugins/AbsDatabase.swift(+140 -6)📝
ios/App/App/plugins/AbsDownloader.swift(+364 -47)📝
ios/App/App/plugins/AbsFileSystem.swift(+49 -23)➖
ios/App/Shared/models/DataClasses.swift(+0 -141)📝
ios/App/Shared/models/DeviceSettings.swift(+11 -18)➕
ios/App/Shared/models/PlaybackMetadata.swift(+14 -0)📝
ios/App/Shared/models/PlaybackSession.swift(+150 -22)➕
ios/App/Shared/models/PlayerSettings.swift(+30 -0)➕
ios/App/Shared/models/PlayerState.swift(+15 -0)📝
ios/App/Shared/models/ServerConnectionConfig.swift(+18 -20)➕
ios/App/Shared/models/download/DownloadItem.swift(+97 -0)➕
ios/App/Shared/models/download/DownloadItemPart.swift(+95 -0)...and 31 more files
📄 Description
iOS Downloading / Improved Progress Syncing
Supports iOS downloads and improved progress syncing. Resolve #178. Fixes #319.
User Interface
abs-ui-readywhen UI is fully mountedAbsAudioPlayer.onReady()when the audio player is fully loaded and all settings have been initializediOS General
async/awaitSwift functionalityRealm
Codeableon Realm objects, as RealmListis notCodeableupdate()anddelete()to make working with Realm objects easier and eliminate the need for DAO pattern, which Realm is not designed around2from1to accommodate the new models required for offline functionalityfrozen()objects andThreadSafeReferencesAbsAudioPlayer
onReady()to fire when UI is fully loadedplaybackSpeedin Realm and references it when neededAbsDatabase
AbsFileSystem
ApiClient
async/awaitsyntax for some endpointsEncodable/Decodableobjects in API endpointsPlayerProgress
MediaProgress, and server objectsPlayerHandler
Native iOS AudioPlayer
currentTime=0with a race condition when initializing the AudioPlayerAbsDownloader
Current task list of remaining items:
PlayerNotificationService.checkCurrentSessionProgressfrom Android🔄 This issue represents a GitHub Pull Request. It cannot be merged through Gitea due to API limitations.