diff --git a/apple/OmnivoreKit/Sources/App/Views/AudioPlayer/MiniPlayer.swift b/apple/OmnivoreKit/Sources/App/Views/AudioPlayer/MiniPlayer.swift index ef4cc9571..c2edd51b8 100644 --- a/apple/OmnivoreKit/Sources/App/Views/AudioPlayer/MiniPlayer.swift +++ b/apple/OmnivoreKit/Sources/App/Views/AudioPlayer/MiniPlayer.swift @@ -27,17 +27,52 @@ public struct MiniPlayer: View { presentingView VStack { Spacer() - if self.audioSession.state != .stopped { + if let item = audioSession.item, self.audioSession.state != .stopped { HStack { - Text("Title of the playing content") + Text(item.unwrappedTitle) .font(.appCallout) - Spacer() + .lineSpacing(1.25) + .foregroundColor(.appGrayTextContrast) + .fixedSize(horizontal: false, vertical: true) + .frame(maxWidth: .infinity, alignment: .leading) + Button( + action: { + switch audioSession.state { + case .playing: + audioSession.pause() + case .paused: + audioSession.unpause() + default: + break + } + }, + label: { + Image(systemName: audioSession.state == .playing ? "pause.circle" : "play.circle") + .font(.appTitleTwo) + } + ) + .frame(width: 28, height: 28) + + Button( + action: { + audioSession.stop() + }, + label: { + Image(systemName: "xmark") + .font(.appTitleTwo) + } + ) + .frame(width: 28, height: 28) } .padding() - .frame(width: geometry.size.width, height: 108) + .frame(width: geometry.size.width, height: 88) // this should be 108 once we add GrabberVisible at the bottom .animation(.spring(), value: true) - .background(Color.systemBackground) - .shadow(color: .gray, radius: 2) + .tint(.appGrayTextContrast) + .background( + Color.systemBackground + .shadow(color: .gray.opacity(0.33), radius: 8, x: 0, y: 4) + .mask(Rectangle().padding(.top, -20)) + ) } } } diff --git a/apple/OmnivoreKit/Sources/App/Views/WebReader/TextToSpeechCoordindator.swift b/apple/OmnivoreKit/Sources/App/Views/WebReader/TextToSpeechCoordindator.swift deleted file mode 100644 index 4edeed0ba..000000000 --- a/apple/OmnivoreKit/Sources/App/Views/WebReader/TextToSpeechCoordindator.swift +++ /dev/null @@ -1,85 +0,0 @@ -// -// TextToSpeechViewModel.swift -// -// -// Created by Jackson Harper on 8/15/22. -// - -import AVFoundation - -public enum TextToSpeechState { - case stopped - case paused - case loading - case playing -} - -public class AudioSession: ObservableObject { - @Published public var state: TextToSpeechState = .stopped - @Published public var audioUrl: URL? - @Published public var title: String? - - var timer: Timer? - var player: AVAudioPlayer? - - deinit { - print("CLOSING DOWN THE SPEECH VIEW MODEL") - self.player?.stop() - } - - func startAudio() { - // Just simulating some loading delay here - DispatchQueue.main.asyncAfter(deadline: .now() + .milliseconds(250)) { - self.audioUrl = Bundle.main.url(forResource: "speech-sample", withExtension: "mp3")! - - if let url = self.audioUrl { - do { - self.player = try AVAudioPlayer(contentsOf: url) - if self.player?.play() ?? false { - self.state = .playing - self.startTimer() - } - } catch { - print(error.localizedDescription) - } - } - } - } - - func startTimer() { - if timer == nil { - // Update every 100ms - timer = Timer.scheduledTimer(timeInterval: 0.1, target: self, selector: #selector(update(_:)), userInfo: nil, repeats: true) - timer?.fire() - } - } - - func stopTimer() { - timer = nil - } - - // Every second, get the current playing time of the player and refresh the status of the player progressslider - @objc func update(_: Timer) { - if let player = player, player.isPlaying { - print("play time in ms: ", Int(player.currentTime * 1000)) - } - } - - func pause() -> Bool { - if let player = player { - player.pause() - state = .paused - return true - } - return false - } - - func playAudio() -> Bool { - if let player = player { - player.play() - state = .playing - return true - } - return false - } -} diff --git a/apple/OmnivoreKit/Sources/App/Views/WebReader/WebReaderContainer.swift b/apple/OmnivoreKit/Sources/App/Views/WebReader/WebReaderContainer.swift index 17236e0fb..876f2021c 100644 --- a/apple/OmnivoreKit/Sources/App/Views/WebReader/WebReaderContainer.swift +++ b/apple/OmnivoreKit/Sources/App/Views/WebReader/WebReaderContainer.swift @@ -56,17 +56,6 @@ struct WebReaderContainerView: View { } } - var ttsButtonIcon: String { - switch audioSession.state { - case .stopped, .paused: - return "play" - case .playing: - return "pause" - case .loading: - return "hourglass.circle" - } - } - var navBar: some View { HStack(alignment: .center) { #if os(iOS) @@ -86,19 +75,24 @@ struct WebReaderContainerView: View { Button( action: { switch audioSession.state { - case .loading: - // Do nothing here - break - case .stopped: - audioSession.startAudio() - case .paused: - audioSession.playAudio() case .playing: - audioSession.pause() + if audioSession.item == self.item { + audioSession.pause() + return + } + fallthrough + case .paused: + if audioSession.item == self.item { + audioSession.unpause() + return + } + fallthrough + default: + audioSession.play(item: self.item) } }, label: { - Image(systemName: ttsButtonIcon) + Image(systemName: audioSession.isPlayingItem(item: item) ? "pause.circle" : "play.circle") .font(.appTitleTwo) } ) diff --git a/apple/OmnivoreKit/Sources/Services/AudioSession/AudioSession.swift b/apple/OmnivoreKit/Sources/Services/AudioSession/AudioSession.swift index 4f7ddd576..4e1525444 100644 --- a/apple/OmnivoreKit/Sources/Services/AudioSession/AudioSession.swift +++ b/apple/OmnivoreKit/Sources/Services/AudioSession/AudioSession.swift @@ -5,8 +5,12 @@ // Created by Jackson Harper on 8/15/22. // +import AVFoundation import Foundation +import Models +import Utils + public enum AudioSessionState { case stopped case paused @@ -16,6 +20,94 @@ public enum AudioSessionState { // Our observable object class public class AudioSession: ObservableObject { - @Published var state: AudioSessionState = .stopped - @Published var title = "This is the title of my session" + @Published public var state: AudioSessionState = .stopped + @Published public var item: LinkedItem? + + @Published public var audioUrl: URL? + + var timer: Timer? + var player: AVAudioPlayer? + + public init() {} + + public func play(item: LinkedItem) { + // Stop any existing session + stop() + + self.item = item + startAudio() + } + + public func stop() { + player?.stop() + timer = nil + player = nil + item = nil + } + + public func isPlayingItem(item: LinkedItem) -> Bool { + state == .playing && self.item == item + } + + public func startAudio() { + state = .loading + + // Just simulating some loading delay here + DispatchQueue.main.asyncAfter(deadline: .now()) { + self.audioUrl = Bundle.main.url(forResource: "speech-sample", withExtension: "mp3")! + if let url = self.audioUrl { + do { + self.player = try AVAudioPlayer(contentsOf: url) + if self.player?.play() ?? false { + self.state = .playing + self.startTimer() + } + } catch { + print(error.localizedDescription) + self.state = .stopped + } + } + } + } + + public func pause() -> Bool { + if let player = player { + player.pause() + state = .paused + return true + } + return false + } + + public func unpause() -> Bool { + playAudio() + } + + public func playAudio() -> Bool { + if let player = player { + player.play() + state = .playing + return true + } + return false + } + + func startTimer() { + if timer == nil { + // Update every 100ms + timer = Timer.scheduledTimer(timeInterval: 0.1, target: self, selector: #selector(update(_:)), userInfo: nil, repeats: true) + timer?.fire() + } + } + + func stopTimer() { + timer = nil + } + + // Every second, get the current playing time of the player and refresh the status of the player progressslider + @objc func update(_: Timer) { + if let player = player, player.isPlaying { + print("play time in ms: ", Int(player.currentTime * 1000)) + } + } } diff --git a/apple/Resources/speech-sample.mp3 b/apple/Resources/speech-sample.mp3 index d371c8543..f4308baa5 100644 Binary files a/apple/Resources/speech-sample.mp3 and b/apple/Resources/speech-sample.mp3 differ