From d11091a4585ac2d4225c2a1698b476049d43d815 Mon Sep 17 00:00:00 2001 From: Jackson Harper Date: Fri, 23 Sep 2022 16:38:21 +0800 Subject: [PATCH] Make tts preload optional, use headphones for tts icon, language ux --- .../Profile/TextToSpeechLanguageView.swift | 1 - .../App/Views/Profile/TextToSpeechView.swift | 46 +----- .../Views/WebReader/WebReaderContainer.swift | 9 +- .../AudioSession/AudioController.swift | 25 +++- .../Sources/Utils/UserDefaultKeys.swift | 1 + .../AppIcon.appiconset/Contents.json | 134 ++++++++++-------- 6 files changed, 106 insertions(+), 110 deletions(-) diff --git a/apple/OmnivoreKit/Sources/App/Views/Profile/TextToSpeechLanguageView.swift b/apple/OmnivoreKit/Sources/App/Views/Profile/TextToSpeechLanguageView.swift index ac27a98c5..abfc581d1 100644 --- a/apple/OmnivoreKit/Sources/App/Views/Profile/TextToSpeechLanguageView.swift +++ b/apple/OmnivoreKit/Sources/App/Views/Profile/TextToSpeechLanguageView.swift @@ -19,7 +19,6 @@ struct TextToSpeechLanguageView: View { .listStyle(InsetListStyle()) #endif } - .navigationTitle("Default Language") } private var innerBody: some View { diff --git a/apple/OmnivoreKit/Sources/App/Views/Profile/TextToSpeechView.swift b/apple/OmnivoreKit/Sources/App/Views/Profile/TextToSpeechView.swift index ae18c137c..744915998 100644 --- a/apple/OmnivoreKit/Sources/App/Views/Profile/TextToSpeechView.swift +++ b/apple/OmnivoreKit/Sources/App/Views/Profile/TextToSpeechView.swift @@ -3,37 +3,17 @@ import Services import SwiftUI import Views -@MainActor final class TextToSpeechViewModel: ObservableObject { - @Published var enableAudioPrefetch: Bool = true -// func cancelSubscription(dataService: DataService) async -> Bool { -// guard let subscriptionName = subscriptionNameToCancel else { return false } -// -// do { -// try await dataService.deleteSubscription(subscriptionName: subscriptionName) -// let index = subscriptions.firstIndex { $0.name == subscriptionName } -// if let index = index { -// subscriptions.remove(at: index) -// } -// return true -// } catch { -// appLogger.debug("failed to remove subscription") -// return false -// } -// } -} - struct TextToSpeechView: View { @EnvironmentObject var audioController: AudioController - @StateObject var viewModel = TextToSpeechViewModel() var body: some View { Group { #if os(iOS) Form { Section("Audio Settings") { - Toggle("Enable audio prefetch", isOn: $viewModel.enableAudioPrefetch) + Toggle("Enable audio prefetch", isOn: $audioController.preloadEnabled) } - NavigationLink(destination: TextToSpeechLanguageView()) { + NavigationLink(destination: TextToSpeechLanguageView().navigationTitle("Default Language")) { Text("Default Language") } innerBody @@ -55,27 +35,5 @@ struct TextToSpeechView: View { } } } -// ForEach(VoiceCategory.allCases, id: \.self) { category in -// Section(category.rawValue) { -// ForEach(audioController.voiceList?.filter { $0.category == category } ?? [], id: \.key.self) { voice in -// Button(action: { -// audioController.currentVoice = voice.key -// // self.showVoiceSheet = false -// }) { -// HStack { -// Text(voice.name) -// -// Spacer() -// -// if voice.selected { -// Image(systemName: "checkmark") -// } -// } -// .contentShape(Rectangle()) -// } -// .buttonStyle(PlainButtonStyle()) -// } -// } -// } } } diff --git a/apple/OmnivoreKit/Sources/App/Views/WebReader/WebReaderContainer.swift b/apple/OmnivoreKit/Sources/App/Views/WebReader/WebReaderContainer.swift index d4449c735..c80e32ef5 100644 --- a/apple/OmnivoreKit/Sources/App/Views/WebReader/WebReaderContainer.swift +++ b/apple/OmnivoreKit/Sources/App/Views/WebReader/WebReaderContainer.swift @@ -82,7 +82,7 @@ struct WebReaderContainerView: View { } }, label: { - Image(systemName: audioController.isPlayingItem(itemID: item.unwrappedID) ? "pause.circle" : "play.circle") + Image(systemName: textToSpeechButtonImage) .font(.appTitleTwo) } ) @@ -91,6 +91,13 @@ struct WebReaderContainerView: View { } } + var textToSpeechButtonImage: String { + if audioController.state == .stopped { + return "headphones" + } + return audioController.isPlayingItem(itemID: item.unwrappedID) ? "pause.circle" : "play.circle" + } + var navBar: some View { HStack(alignment: .center) { #if os(iOS) diff --git a/apple/OmnivoreKit/Sources/Services/AudioSession/AudioController.swift b/apple/OmnivoreKit/Sources/Services/AudioSession/AudioController.swift index dd714c046..df8e3ba6f 100644 --- a/apple/OmnivoreKit/Sources/Services/AudioSession/AudioController.swift +++ b/apple/OmnivoreKit/Sources/Services/AudioSession/AudioController.swift @@ -312,7 +312,7 @@ public class AudioController: NSObject, ObservableObject, AVAudioPlayerDelegate if let stoppedId = stoppedId { EventTracker.track( - .audioSessionEnd(linkID: stoppedId, timeElapsed: stoppedTimeElapsed ?? 0.0) + .audioSessionEnd(linkID: stoppedId, timeElapsed: stoppedTimeElapsed) ) } } @@ -327,8 +327,11 @@ public class AudioController: NSObject, ObservableObject, AVAudioPlayerDelegate } public func preload(itemIDs: [String], retryCount _: Int = 0) async -> Bool { + if !preloadEnabled { + return true + } + for itemID in itemIDs { - print("preloading speech file: ", itemID) if let document = try? await downloadSpeechFile(itemID: itemID, priority: .low) { let synthesizer = SpeechSynthesizer(appEnvironment: appEnvironment, networker: networker, document: document) do { @@ -450,6 +453,8 @@ public class AudioController: NSObject, ObservableObject, AVAudioPlayerDelegate } } + @AppStorage(UserDefaultKey.textToSpeechPreloadEnabled.rawValue) public var preloadEnabled = true + public var currentVoiceLanguage: VoiceLanguage { VOICELANGUAGES.first(where: { $0.key == currentLanguage }) ?? ENGLISH } @@ -556,6 +561,20 @@ public class AudioController: NSObject, ObservableObject, AVAudioPlayerDelegate return "en-US-CoraNeural" } + public func playVoiceSample(voice: String) { + do { + if let url = Bundle.main.url(forResource: "tts-voice-sample-\(voice)", withExtension: "mp3") { + let player = try AVAudioPlayer(contentsOf: url, fileTypeHint: AVFileType.mp3.rawValue) + player.play() + } else { + NSNotification.operationFailed(message: "Error playing voice sample.") + } + } catch { + print("ERROR", error) + NSNotification.operationFailed(message: "Error playing voice sample.") + } + } + private func updateDurations(oldPlayback: Double, newPlayback: Double) { if let oldDurations = durations { durations = oldDurations.map { $0 * oldPlayback / newPlayback } @@ -846,7 +865,6 @@ public class AudioController: NSObject, ObservableObject, AVAudioPlayerDelegate func downloadSpeechFile(itemID: String, priority: DownloadPriority) async throws -> SpeechDocument? { let decoder = JSONDecoder() let speechFileUrl = pathForSpeechFile(itemID: itemID) - print("looking up speeh file: ", speechFileUrl) if FileManager.default.fileExists(atPath: speechFileUrl.path) { let data = try Data(contentsOf: speechFileUrl) @@ -858,7 +876,6 @@ public class AudioController: NSObject, ObservableObject, AVAudioPlayerDelegate } let path = "/api/article/\(itemID)/speech?voice=\(currentVoice)&secondaryVoice=\(secondaryVoice)&priority=\(priority)\(isoLangForCurrentVoice())" - print("fetching audio for path", path) guard let url = URL(string: path, relativeTo: appEnvironment.serverBaseURL) else { throw BasicError.message(messageText: "Invalid audio URL") } diff --git a/apple/OmnivoreKit/Sources/Utils/UserDefaultKeys.swift b/apple/OmnivoreKit/Sources/Utils/UserDefaultKeys.swift index 77b415035..caef28d45 100644 --- a/apple/OmnivoreKit/Sources/Utils/UserDefaultKeys.swift +++ b/apple/OmnivoreKit/Sources/Utils/UserDefaultKeys.swift @@ -16,4 +16,5 @@ public enum UserDefaultKey: String { case textToSpeechPlaybackRate case textToSpeechPreferredVoice case textToSpeechDefaultLanguage + case textToSpeechPreloadEnabled } diff --git a/apple/Resources/Assets.xcassets/AppIcon.appiconset/Contents.json b/apple/Resources/Assets.xcassets/AppIcon.appiconset/Contents.json index 71ce7a1d6..913e238c9 100644 --- a/apple/Resources/Assets.xcassets/AppIcon.appiconset/Contents.json +++ b/apple/Resources/Assets.xcassets/AppIcon.appiconset/Contents.json @@ -150,6 +150,66 @@ "scale" : "1x", "size" : "1024x1024" }, + { + "filename" : "image 1-1.png", + "idiom" : "mac", + "scale" : "1x", + "size" : "16x16" + }, + { + "filename" : "image 1@2x-1.png", + "idiom" : "mac", + "scale" : "2x", + "size" : "16x16" + }, + { + "filename" : "image 1.png", + "idiom" : "mac", + "scale" : "1x", + "size" : "32x32" + }, + { + "filename" : "image 1@2x.png", + "idiom" : "mac", + "scale" : "2x", + "size" : "32x32" + }, + { + "filename" : "128.png", + "idiom" : "mac", + "scale" : "1x", + "size" : "128x128" + }, + { + "filename" : "128@2x.png", + "idiom" : "mac", + "scale" : "2x", + "size" : "128x128" + }, + { + "filename" : "256.png", + "idiom" : "mac", + "scale" : "1x", + "size" : "256x256" + }, + { + "filename" : "256@2x.png", + "idiom" : "mac", + "scale" : "2x", + "size" : "256x256" + }, + { + "filename" : "512-1.png", + "idiom" : "mac", + "scale" : "1x", + "size" : "512x512" + }, + { + "filename" : "512@2x.png", + "idiom" : "mac", + "scale" : "2x", + "size" : "512x512" + }, { "filename" : "48.png", "idiom" : "watch", @@ -225,6 +285,13 @@ "size" : "51x51", "subtype" : "45mm" }, + { + "idiom" : "watch", + "role" : "appLauncher", + "scale" : "2x", + "size" : "54x54", + "subtype" : "49mm" + }, { "filename" : "172.png", "idiom" : "watch", @@ -256,71 +323,18 @@ "size" : "117x117", "subtype" : "45mm" }, + { + "idiom" : "watch", + "role" : "quickLook", + "scale" : "2x", + "size" : "129x129", + "subtype" : "49mm" + }, { "filename" : "1024.png", "idiom" : "watch-marketing", "scale" : "1x", "size" : "1024x1024" - }, - { - "filename" : "image 1-1.png", - "idiom" : "mac", - "scale" : "1x", - "size" : "16x16" - }, - { - "filename" : "image 1@2x-1.png", - "idiom" : "mac", - "scale" : "2x", - "size" : "16x16" - }, - { - "filename" : "image 1.png", - "idiom" : "mac", - "scale" : "1x", - "size" : "32x32" - }, - { - "filename" : "image 1@2x.png", - "idiom" : "mac", - "scale" : "2x", - "size" : "32x32" - }, - { - "filename" : "128.png", - "idiom" : "mac", - "scale" : "1x", - "size" : "128x128" - }, - { - "filename" : "128@2x.png", - "idiom" : "mac", - "scale" : "2x", - "size" : "128x128" - }, - { - "filename" : "256.png", - "idiom" : "mac", - "scale" : "1x", - "size" : "256x256" - }, - { - "filename" : "256@2x.png", - "idiom" : "mac", - "scale" : "2x", - "size" : "256x256" - }, - { - "filename" : "512-1.png", - "idiom" : "mac", - "scale" : "1x", - "size" : "512x512" - }, - { - "filename" : "512@2x.png", - "idiom" : "mac", - "scale" : "2x", - "size" : "512x512" } ], "info" : {