mirror of
https://github.com/omnivore-app/omnivore.git
synced 2026-03-11 08:54:26 +00:00
Make tts preload optional, use headphones for tts icon, language ux
This commit is contained in:
parent
461100d359
commit
d11091a458
6 changed files with 106 additions and 110 deletions
|
|
@ -19,7 +19,6 @@ struct TextToSpeechLanguageView: View {
|
|||
.listStyle(InsetListStyle())
|
||||
#endif
|
||||
}
|
||||
.navigationTitle("Default Language")
|
||||
}
|
||||
|
||||
private var innerBody: some View {
|
||||
|
|
|
|||
|
|
@ -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())
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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")
|
||||
}
|
||||
|
|
|
|||
|
|
@ -16,4 +16,5 @@ public enum UserDefaultKey: String {
|
|||
case textToSpeechPlaybackRate
|
||||
case textToSpeechPreferredVoice
|
||||
case textToSpeechDefaultLanguage
|
||||
case textToSpeechPreloadEnabled
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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" : {
|
||||
|
|
|
|||
Loading…
Reference in a new issue