[PR #277] [MERGED] iOS Downloads / Improved Progress Syncing #1395

Closed
opened 2026-04-24 23:58:10 +02:00 by adam · 0 comments
Owner

📋 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: masterHead: ios-downloads


📝 Commits (10+)

  • 2ca9ce7 feat: iOS download groundwork
  • 16ca746 Convert back to Swift objects
  • affcdea Fix class name collisions
  • f40da56 Fix missing braces
  • 29f08a5 Fix use of optionals
  • a29a766 Add unrealm to use Swift object in db
  • f56561b Refactor into smaller functions
  • e620f53 Get something saving in the database
  • 175e642 First pass at attempting to save library items
  • b7725c4 Prototype 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

  • 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
  • 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

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:

  • 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>
adam added the pull-request label 2026-04-24 23:58:10 +02:00
adam closed this issue 2026-04-24 23:58:10 +02:00
Sign in to join this conversation.
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: starred/audiobookshelf-app#1395