mirror of
https://github.com/omnivore-app/omnivore.git
synced 2026-03-11 08:54:26 +00:00
Setup a single AudioSession, a little improvement on the MiniPlayer UI
This commit is contained in:
parent
06036b7517
commit
d918b8b0a8
5 changed files with 149 additions and 113 deletions
|
|
@ -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))
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
}
|
||||
|
|
@ -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)
|
||||
}
|
||||
)
|
||||
|
|
|
|||
|
|
@ -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))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Binary file not shown.
Loading…
Reference in a new issue