Make offline audio download a user action during beta

This gets us past our cold start problem with audio, giving the
user the control to start downloading items.
This commit is contained in:
Jackson Harper
2022-08-30 23:03:12 +08:00
parent 386a47b73b
commit e46d7c7c9e
5 changed files with 71 additions and 6 deletions

View File

@ -137,6 +137,35 @@ import Views
}
}
}
.sheet(isPresented: $viewModel.showAudioInfoAlert) {
VStack {
Text("Welcome to the Omnivore text to speech beta.")
.font(.appTitle)
Spacer()
Text(
"""
This build introduces offline text to speech files. Normally these files will\
be downloaded in the background and made available offline.
During the beta these files can be manually downloaded by long pressing on an item and\
choosing Download Audio, or by tapping the play button. When you first tap the\
play button, the audio will be generated and downloaded. This can take some time.
Future versions will do this in the background.
""")
Text("")
Spacer()
Button(
action: { viewModel.dismissAudioInfoAlert() },
label: { Text("Dismiss").frame(maxWidth: .infinity) }
)
.buttonStyle(RoundedRectButtonStyle())
}.padding(24)
}
.task {
if viewModel.items.isEmpty {
loadItems(isRefresh: true)
@ -223,6 +252,8 @@ import Views
struct HomeFeedListView: View {
@EnvironmentObject var dataService: DataService
@EnvironmentObject var audioSession: AudioSession
@Binding var prefersListLayout: Bool
@State private var itemToRemove: LinkedItem?
@ -276,6 +307,10 @@ import Views
Label { Text("Snooze") } icon: { Image.moon }
}
}
Button(
action: { viewModel.downloadAudio(audioSession: audioSession, item: item) },
label: { Label("Download Audio", systemImage: "icloud.and.arrow.down") }
)
}
.swipeActions(edge: .trailing, allowsFullSwipe: true) {
if !item.isArchived {
@ -360,6 +395,8 @@ import Views
viewModel.itemUnderLabelEdit = item
case .editTitle:
viewModel.itemUnderTitleEdit = item
case .downloadAudio:
viewModel.downloadAudio(audioSession: audioSession, item: item)
}
}

View File

@ -26,6 +26,7 @@ import Views
@Published var linkRequest: LinkRequest?
@Published var showLoadingBar = false
@Published var appliedSort = LinkedItemSort.newest.rawValue
@AppStorage(UserDefaultKey.audioInfoAlertShown.rawValue) var showAudioInfoAlert = false
@AppStorage(UserDefaultKey.lastSelectedLinkedItemFilter.rawValue) var appliedFilter = LinkedItemFilter.inbox.rawValue
@ -55,7 +56,7 @@ import Views
items.insert(item, at: 0)
}
func loadItems(dataService: DataService, audioSession _: AudioSession, isRefresh: Bool) async {
func loadItems(dataService: DataService, audioSession: AudioSession, isRefresh: Bool) async {
let syncStartTime = Date()
let thisSearchIdx = searchIdx
searchIdx += 1
@ -123,7 +124,13 @@ import Views
cursor = queryResult.cursor
if let username = dataService.currentViewer?.username {
await dataService.prefetchPages(itemIDs: newItems.map(\.unwrappedID), username: username)
// await audioSession.preload(itemIDs: newItems.map(\.unwrappedID))
// Only preload the first item in the list. We are doing this during the beta
// because it will kick off the user's future items being automatically transcribed.
// This happens because when an article is saved, we check if the user has a recent
// listen. If they do, we will automatically transcribe their message.
if let first = newItems.first?.id {
_ = await audioSession.preload(itemIDs: [first])
}
}
} else {
updateFetchController(dataService: dataService)
@ -133,6 +140,19 @@ import Views
showLoadingBar = false
}
func dismissAudioInfoAlert() {
UserDefaults.standard.set(true, forKey: UserDefaultKey.audioInfoAlertShown.rawValue)
showAudioInfoAlert = false
}
func downloadAudio(audioSession: AudioSession, item: LinkedItem) {
Snackbar.show(message: "Downloading Offline Audio")
Task {
let downloaded = await audioSession.preload(itemIDs: [item.unwrappedID])
Snackbar.show(message: downloaded ? "Audio file downloaded" : "Error downloading audio")
}
}
private var fetchRequest: NSFetchRequest<Models.LinkedItem> {
let fetchRequest: NSFetchRequest<Models.LinkedItem> = LinkedItem.fetchRequest()

View File

@ -76,7 +76,7 @@ public class AudioSession: NSObject, ObservableObject, AVAudioPlayerDelegate {
downloadTask?.cancel()
}
public func preload(itemIDs: [String], retryCount: Int = 0) async {
public func preload(itemIDs: [String], retryCount: Int = 0) async -> Bool {
var pendingList = [String]()
for pageId in itemIDs {
@ -96,23 +96,25 @@ public class AudioSession: NSObject, ObservableObject, AVAudioPlayerDelegate {
if let result = result, result.pending {
print("audio file is pending download: ", pageId)
pendingList.append(pageId)
} else {
print("audio file is downloaded: ", pageId)
}
}
print("audio files pending download: ", pendingList)
if pendingList.isEmpty {
return
return true
}
if retryCount > 5 {
print("reached max preload depth, stopping preloading")
return
return false
}
let retryDelayInNanoSeconds = UInt64(retryCount * 2 * 1_000_000_000)
try? await Task.sleep(nanoseconds: retryDelayInNanoSeconds)
await preload(itemIDs: pendingList, retryCount: retryCount + 1)
return await preload(itemIDs: pendingList, retryCount: retryCount + 1)
}
public var localAudioUrl: URL? {

View File

@ -13,4 +13,5 @@ public enum UserDefaultKey: String {
case lastUsedAppVersion
case lastUsedAppBuildNumber
case lastItemSyncTime
case audioInfoAlertShown
}

View File

@ -7,6 +7,7 @@ public enum GridCardAction {
case delete
case editLabels
case editTitle
case downloadAudio
}
public struct GridCard: View {
@ -65,6 +66,10 @@ public struct GridCard: View {
action: { menuActionHandler(.delete) },
label: { Label("Delete", systemImage: "trash") }
)
Button(
action: { menuActionHandler(.downloadAudio) },
label: { Label("Download Audio", systemImage: "icloud.and.arrow.down") }
)
}
}