How is Siri is able to determine when I'm finished speaking. The reason I would like to know is that I would like to implement similar functionality with Apple's Speech Recognition API with my app. Is this doable, or is the only way to know when the user has stopped speaking is via user input?
You can use a timer, i had the same problem and I could not solve it with an elegant method.
fileprivate var timer:Timer?
func startRecordingTimer() {
lastString = ""
createTimerTimer(4)
}
func stopRecordingTimer() {
timer?.invalidate()
timer = nil
}
fileprivate func whileRecordingTimer() {
createTimerTimer(2)
}
fileprivate var lastString = ""
func createTimerTimer(_ interval:Double) {
OperationQueue.main.addOperation({[unowned self] in
self.timer?.invalidate()
self.timer = Timer.scheduledTimer(withTimeInterval: interval, repeats: false) { (_) in
self.timer?.invalidate()
if(self.lastString.characters.count > 0){
//DO SOMETHING
}else{
self.whileRecordingTimer()
}
}
})
}
and in SFSpeechRecognitionTaskDelegate
public func speechRecognitionTask(_ task: SFSpeechRecognitionTask, didHypothesizeTranscription transcription: SFTranscription) {
let result = transcription.formattedString
lastString = result
}
Related
so I'm reading the Modern Concurrency book from raywenderlich.com and I assume the book must be outdated or something, I'm trying to run the closure insde the AsyncStream but it doesn't seem to get there, I'm still pretty new to this Async/Await thing, but when adding some breakpoints I can see my code is not getting there. This is my code and a screenshot with some warnings showing. I am not really familiar with what the warnings mean, just trying to learn all this new stuff, I would truly appreciate some help and is there a way to fix it with Swift 6? Thanks in advance!
Reference to captured var 'countdown' in concurrently-executing code; this is an error in Swift 6
Mutation of captured var 'countdown' in concurrently-executing code; this is an error in Swift 6
func countdown(to message: String) async throws {
guard !message.isEmpty else { return }
var countdown = 3
let counter = AsyncStream<String> { continuation in
Timer.scheduledTimer(withTimeInterval: 1.0, repeats: true) { timer in
continuation.yield("\(countdown)...")
countdown -= 1
}
}
for await countDownMessage in counter {
try await say(countDownMessage)
}
}
Timer.scheduleTimer requires that it be scheduled on a run loop. In practical terms, that means we would want to schedule it on the main thread’s run loop. So, you either call scheduleTimer from the main thread, or create a Timer and manually add(_:forMode:) it to RunLoop.main . See the Scheduling Timers in Run Loops section of the Timer documentation.
The easiest way would be to just isolate this function to the main actor. E.g.,
#MainActor
func countdown(to message: String) async throws { … }
There a few other issues here, too:
I would suggest defining the countdown variable within the AsyncStream:
#MainActor
func countdown(to message: String) async throws {
guard !message.isEmpty else { return }
let counter = AsyncStream<String> { continuation in
var countdown = 3
Timer.scheduledTimer(withTimeInterval: 1.0, repeats: true) { timer in
continuation.yield("\(countdown)...")
countdown -= 1
}
}
for await countDownMessage in counter {
try await say(countDownMessage)
}
}
The AsyncStream is never finished. You might want to finish it when it hits zero:
#MainActor
func countdown(to message: String) async throws {
guard !message.isEmpty else { return }
let counter = AsyncStream<String> { continuation in
var countdown = 3
Timer.scheduledTimer(withTimeInterval: 1.0, repeats: true) { timer in
continuation.yield("\(countdown)...")
// presumably you want this countdown timer to finish when it hits zero
guard countdown > 0 else {
timer.invalidate()
continuation.finish()
return
}
// otherwise, decrement and carry on
countdown -= 1
}
}
for await countDownMessage in counter {
try await say(countDownMessage)
}
}
There should be a continuation.onTermination closure to handle cancelation of the asynchronous sequence.
#MainActor
func countdown(to message: String) async throws {
guard !message.isEmpty else { return }
let counter = AsyncStream<String> { continuation in
var countdown = 3
let timer = Timer.scheduledTimer(withTimeInterval: 1.0, repeats: true) { timer in
continuation.yield("\(countdown)...")
// presumably you want this countdown timer to finish when it hits zero
guard countdown > 0 else {
timer.invalidate()
continuation.finish()
return
}
// otherwise, decrement and carry on
countdown -= 1
}
continuation.onTermination = { _ in
timer.invalidate()
}
}
for await countDownMessage in counter {
try await say(countDownMessage)
}
}
Going back to the original question (why this is not running), I personally would avoid the use of Timer in conjunction with Swift concurrency at all. A GCD timer would be better, as it doesn’t require a RunLoop. Even better, I would advise Task.sleep. Needless to say, that is designed to work with Swift concurrency, and also is cancelable.
I personally would suggest something like:
func countdown(to message: String) async throws {
guard !message.isEmpty else { return }
let counter = AsyncStream<String> { continuation in
let task = Task {
for countdown in (0...3).reversed() {
try await Task.sleep(for: .seconds(1))
continuation.yield("\(countdown)...")
}
continuation.finish()
}
continuation.onTermination = { _ in
task.cancel()
}
}
for await countDownMessage in counter {
try await say(countDownMessage)
}
}
I am trying to cancel a delayed execution of a function running on the main queue, in a tap gesture, I found a way to create a cancellable DispatchWorkItem, but the issue I have is that it's getting created every time while tapping, and then when I cancel the execution, I actually cancel the new delayed execution and not the first one.
Here is a simpler example with a Timer instead of a DispatchQueue.main.asyncAfter:
.onTapGesture {
isDeleting.toggle()
let timer = Timer.scheduledTimer(withTimeInterval: 2.0, repeats: false) { timer in
completeTask()
}
if !isDeleting {
timer.invalidate()
}
}
completeTask:
private func completeTask() {
tasksViewModel.deleteTask(task: task) // task is declared above this func at the top level of the struct and so is tasksViewModel, etc.
guard let userID = userViewModel.id?.uuidString else { return }
Task {
//do some async stuff
}
}
As you can see if I click it once the timer fires, but if I click it again, another timer fires and straight away invalidates, but the first timer is still running.
So I have to find a way to create only one instance of that timer.
I tried putting it in the top level of the struct and not inside the var body but the issue now is that I can't use completeTask() because it uses variables that are declared at the same scope.
Also, can't use a lazy initialization because it is an immutable struct.
My goal is to eventually let the user cancel a timed task and reactivate it at will on tapping a button/view. Also, the timed task should use variables that are declared at the top level of the struct.
First of all you need to create a strong reference of timer on local context like so:
var timer: Timer?
and then, set the timer value on onTapGesture closure:
.onTapGesture {
isDeleting.toggle()
self.timer = Timer.scheduledTimer(withTimeInterval: 2.0, repeats: false) { timer in
completeTask()
}
if !isDeleting {
timer.invalidate()
}
}
and after that you can invalidate this Timer whenever you need by accessing the local variable timer like this:
func doSomething() {
timer?.invalidate()
}
that is my solution mb can help you
var timer: Timer?
private func produceWorkItem(withDelay: Double = 3) {
scrollItem?.cancel()
timer?.invalidate()
scrollItem = DispatchWorkItem.init { [weak self] in
self?.timer = Timer.scheduledTimer(withTimeInterval: withDelay, repeats: false) { [weak self] _ in
self?.goToNextPage(animated: true, completion: { [weak self] _ in self?.produceWorkItem() })
guard let currentVC = self?.viewControllers?.first,
let index = self?.pages.firstIndex(of: currentVC) else {
return
}
self?.pageControl.currentPage = index
}
}
scrollItem?.perform()
}
for stop use scrollItem?.cancel()
for start call func
The environment for this is iOS 13.6 and Swift 5. I have a very simple app that successfully plays an MP3 file in the foreground or background. I added MPRemoteCommandCenter play and pause command handlers to it. I play the sound file in the foreground and then pause it.
When I tap the play button from the lock screen, my code calls audioPlayer.play(), which returns true. I hear the sound start playing again, but the currentTime of the player does not advance. After that, the play and pause buttons on the lock screen do nothing. When I foreground the app again, the play button plays from where it was before I went to the lock screen.
Here is my AudioPlayer class:
import AVFoundation
import MediaPlayer
class AudioPlayer: RemoteAudioCommandDelegate {
var audioPlayer = AVAudioPlayer()
let remoteCommandHandler = RemoteAudioCommandHandler()
var timer:Timer!
func play(title: String) {
let path = Bundle.main.path(forResource: title, ofType: "mp3")!
let url = URL(fileURLWithPath: path)
do {
try AVAudioSession.sharedInstance().setCategory(AVAudioSession.Category.playback)
try AVAudioSession.sharedInstance().setActive(true)
audioPlayer = try AVAudioPlayer(contentsOf: url)
remoteCommandHandler.delegate = self
remoteCommandHandler.enableDisableRemoteCommands(true)
timer = Timer.scheduledTimer(timeInterval: 1.0, target: self, selector: #selector(updateNowPlayingInfo), userInfo: nil, repeats: true)
} catch let error as NSError {
print("error = \(error)")
}
}
func play() {
print ("audioPlayer.play() returned \(audioPlayer.play())")
}
func pause() {
audioPlayer.pause()
}
func stop() {
audioPlayer.stop()
}
func currentTime() -> TimeInterval {
return audioPlayer.currentTime
}
func setCurrentTime(_ time:TimeInterval) {
audioPlayer.currentTime = time
}
#objc func updateNowPlayingInfo() {
// Hard-code the nowPlayingInfo since this is a simple test app
var nowPlayingDict =
[MPMediaItemPropertyTitle: "Tin Man",
MPMediaItemPropertyAlbumTitle: "The Complete Greatest Hits",
MPMediaItemPropertyAlbumTrackNumber: NSNumber(value: UInt(10) as UInt),
MPMediaItemPropertyArtist: "America",
MPMediaItemPropertyPlaybackDuration: 208,
MPNowPlayingInfoPropertyPlaybackRate: NSNumber(value: 1.0 as Float)] as [String : Any]
nowPlayingDict[MPNowPlayingInfoPropertyElapsedPlaybackTime] = NSNumber(value: audioPlayer.currentTime as Double)
MPNowPlayingInfoCenter.default().nowPlayingInfo = nowPlayingDict
}
}
Here is my RemoteCommandHandler class:
import Foundation
import MediaPlayer
protocol RemoteAudioCommandDelegate: class {
func play()
func pause()
}
class RemoteAudioCommandHandler: NSObject {
weak var delegate: RemoteAudioCommandDelegate?
var remoteCommandCenter = MPRemoteCommandCenter.shared()
var playTarget: Any? = nil
var pauseTarget: Any? = nil
func enableDisableRemoteCommands(_ enabled: Bool) {
print("Called with enabled = \(enabled)")
remoteCommandCenter.playCommand.isEnabled = enabled
remoteCommandCenter.pauseCommand.isEnabled = enabled
if enabled {
addRemoteCommandHandlers()
} else {
removeRemoteCommandHandlers()
}
}
fileprivate func addRemoteCommandHandlers() {
print( "Entered")
if playTarget == nil {
print( "Adding playTarget")
playTarget = remoteCommandCenter.playCommand.addTarget { (event) -> MPRemoteCommandHandlerStatus in
print("addRemoteCommandHandlers calling delegate play")
self.delegate?.play()
return .success
}
}
if pauseTarget == nil {
print( "Adding pauseTarget")
pauseTarget = remoteCommandCenter.pauseCommand.addTarget { (event) -> MPRemoteCommandHandlerStatus in
print("addRemoteCommandHandlers calling delegate pause")
self.delegate?.pause()
return .success
}
}
}
fileprivate func removeRemoteCommandHandlers() {
print( "Entered")
if playTarget != nil {
print( "Removing playTarget")
remoteCommandCenter.playCommand.removeTarget(playTarget)
playTarget = nil
}
if pauseTarget != nil {
print( "Removing pauseTarget")
remoteCommandCenter.pauseCommand.removeTarget(pauseTarget)
pauseTarget = nil
}
}
}
I will gladly supply further required info, because I'm baffled at why this relatively straightforward code (in my mind) code doesn't work.
Assistance is much appreciated!
After some more debugging, I found that the AVAudioPlayer started to play the sound from the lock screen, but stopped again shortly after.
I mitigated the problem by adding a Timer. The timer checks if the last command by the user was play, but the sound is not playing. I also change the status when the user selects pause or the song stops playing at its natural end.
I am still at a loss for an actual fix for this problem.
I am trying to add(move forward) 10 second song duration or minus(move backward) 10 second in Spotify player but i am really confused how to add or minus.
When i m trying to use this code the song is not changed duration
// forward button action
#IBAction func moveFrdBtnAction(_ sender: Any) {
SpotifyManager.shared.audioStreaming(SpotifyManager.shared.player, didSeekToPosition: TimeInterval(10))
}
// spotify delegate method seekToPosition
func audioStreaming(_ audioStreaming: SPTAudioStreamingController!, didSeekToPosition position: TimeInterval) {
player?.seek(to: position, callback: { (error) in
let songDuration = audioStreaming.metadata.currentTrack?.duration as Any as! Double
self.delegate?.getSongTime(timeCount: Int(songDuration)+1)
})
}
We are making a music application using the same SDK in both the platforms (Android & iOS), the seekToPosition method of the Spotify SDK is working correctly in the Android version, however, it is not working in the iOS one.The delegate method calls itself but the music stops.
Can you kindly let us know why this scenario is happening, and what should we do to run it on the iOS devices as well.
Can someone please explain to me how to solve this , i've tried to solve this but no results yet.
Any help would be greatly appreciated.
Thanks in advance.
I don't use this API so my answer will be based your code and Spotify's reference documentation.
I think there are a few things wrong with your flow:
As Robert Dresler commented, you should (approximately) never call a delegate directly, a delegate calls you.
I'm pretty sure your action currently results in jumping to exactly 10 seconds, not by 10 seconds.
(As an aside, I'd suggest changing the name of your function moveFrdBtnAction to at least add more vowels)
Anyway, here's my best guess at what you want:
// forward button action
#IBAction func moveForwardButtonAction(_ sender: Any) {
skipAudio(by: 10)
}
#IBAction func moveBackButtonAction(_ sender: Any) {
skipAudio(by: -10)
}
func skipAudio(by interval: TimeInterval) {
if let player = player {
let position = player.playbackState.position // The documentation alludes to milliseconds but examples don't.
player.seek(to: position + interval, callback: { (error) in
// Handle the error (if any)
})
}
}
// spotify delegate method seekToPosition
func audioStreaming(_ audioStreaming: SPTAudioStreamingController!, didSeekToPosition position: TimeInterval) {
// Update your UI
}
Note that I have not handled seeking before the start of the track, nor after the end which could happen with a simple position + interval. The API may handle this for you, or not.
You could take a look at the examples here: spotify/ios-sdk. In the NowPlayingView example they use the 'seekForward15Seconds', maybe you could use that? If you still need 10s I have added a function below. The position is in milliseconds.
"position: The position to seek to in milliseconds"
docs
ViewController.swift
var appRemote: SPTAppRemote {
get {
return AppDelegate.sharedInstance.appRemote
}
}
fileprivate func seekForward15Seconds() {
appRemote.playerAPI?.seekForward15Seconds(defaultCallback)
}
fileprivate seekBackward15Seconds() {
appRemote.playerAPI?.seekBackward15Seconds(defaultCallback)
}
// TODO: Or you could try this function
func seekForward(seconds: Int){
appRemote.playerAPI?.getPlayerState({ (result, error) in
// playback position in milliseconds
let current_position = self.playerState?.playbackPosition
let seconds_in_milliseconds = seconds * 1000
self.appRemote.playerAPI?.seek(toPosition: current_position + seconds_in_milliseconds, callback: { (result, error) in
guard error == nil else {
print(error)
return
}
})
})
}
var defaultCallback: SPTAppRemoteCallback {
get {
return {[weak self] _, error in
if let error = error {
self?.displayError(error as NSError)
}
}
}
}
AppDelegate.swift
lazy var appRemote: SPTAppRemote = {
let configuration = SPTConfiguration(clientID: self.clientIdentifier, redirectURL: self.redirectUri)
let appRemote = SPTAppRemote(configuration: configuration, logLevel: .debug)
appRemote.connectionParameters.accessToken = self.accessToken
appRemote.delegate = self
return appRemote
}()
class var sharedInstance: AppDelegate {
get {
return UIApplication.shared.delegate as! AppDelegate
}
}
Edit1:
For this to work you need to follow the Prepare Your Environment:
Add the SpotifyiOS.framework to your Xcode project
Hope it helps!
I'm trying to create a typewriter animation effect with a UILabel, but can't find any answers. Is the UILabel the correct object to use? I want the text to print to the screen an array of strings like, "Logging in... Opening Folder... Rebooting system.." etc. I should mention that I'm new to coding and I've tried searching the Documentation and API reference but no luck. I'm currently learning SWIFT if thats worth mentioning
Based on this Answer:
Letter by letter animation for UILabel?
I've updated it to Swift 4 and solved the CPU animation problem with DispatchWorkItem in order to create a queue.
Swift 4
extension UILabel {
func setTextWithTypeAnimation(typedText: String, characterDelay: TimeInterval = 5.0) {
text = ""
var writingTask: DispatchWorkItem?
writingTask = DispatchWorkItem { [weak weakSelf = self] in
for character in typedText {
DispatchQueue.main.async {
weakSelf?.text!.append(character)
}
Thread.sleep(forTimeInterval: characterDelay/100)
}
}
if let task = writingTask {
let queue = DispatchQueue(label: "typespeed", qos: DispatchQoS.userInteractive)
queue.asyncAfter(deadline: .now() + 0.05, execute: task)
}
}
}
Usage
label.setTextWithTypeAnimation(typedText: text, characterDelay: 10) //less delay is faster
Swift 5
func setTyping(text: String, characterDelay: TimeInterval = 5.0) {
self.text = ""
let writingTask = DispatchWorkItem { [weak self] in
text.forEach { char in
DispatchQueue.main.async {
self?.text?.append(char)
}
Thread.sleep(forTimeInterval: characterDelay/100)
}
}
let queue: DispatchQueue = .init(label: "typespeed", qos: .userInteractive)
queue.asyncAfter(deadline: .now() + 0.05, execute: writingTask)
}
Usage
label.setTyping(text: "Your text")
update: Xcode 7.0 GM • Swift 2.0
import UIKit
class ViewController: UIViewController {
#IBOutlet weak var myTypeWriter: UITextField!
let myText = Array("Hello World !!!".characters)
var myCounter = 0
var timer:NSTimer?
func fireTimer(){
timer = NSTimer.scheduledTimerWithTimeInterval(0.5, target: self, selector: "typeLetter", userInfo: nil, repeats: true)
}
func typeLetter(){
if myCounter < myText.count {
myTypeWriter.text = myTypeWriter.text! + String(myText[myCounter])
let randomInterval = Double((arc4random_uniform(8)+1))/20
timer?.invalidate()
timer = NSTimer.scheduledTimerWithTimeInterval(randomInterval, target: self, selector: "typeLetter", userInfo: nil, repeats: false)
} else {
timer?.invalidate()
}
myCounter++
}
override func viewDidLoad() {
super.viewDidLoad()
fireTimer()
// Do any additional setup after loading the view, typically from a nib.
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
}
I have written a subclass of UILabel called CLTypingLabel, available on GitHub. This should do what you want.
After installing CocoaPods, add the following like to your Podfile to use it:
pod 'CLTypingLabel'
Sample Code
Change the class of a label from UILabel to CLTypingLabel;
#IBOutlet weak var myTypeWriterLabel: CLTypingLabel!
At runtime, set text of the label will trigger animation automatically:
myTypeWriterLabel.text = "This is a demo of typing label animation..."
You can customize time interval between each character:
myTypeWriterLabel.charInterval = 0.08 //optional, default is 0.1
You can pause the typing animation at any time:
myTypeWriterLabel.pauseTyping() //this will pause the typing animation
myTypeWriterLabel.continueTyping() //this will continue paused typing animation
Also there is a sample project that comes with cocoapods
my version of the typewriter effect animation using a timer:
var text = "text"
_ = Timer.scheduledTimer(
withTimeInterval: 0.1,
repeats: true
) { [weak self] timer in
let char = text.removeFirst()
self?.yourLabel.text?.append(char.description)
if text.isEmpty {
timer.invalidate()
}
}