ndarilek / tts-rs

115 stars 25 forks source link

MacOS: Only first phrase is being pronounced #40

Open ninjaboy opened 1 year ago

ninjaboy commented 1 year ago

Tried using it my macos console app and noticed that only first occurence of tts.speak produces sound. Retried with simple example and the problem is reproducible

image
use std::{thread, time};

use tts::*;

fn main() -> Result<(), Error> {
    // env_logger::init();
    let mut tts = Tts::default()?;
    let mut phrase = 1;
    loop {
        println!("{phrase}");
        tts.speak(format!("Phrase {}", phrase), false)?;
        let time = time::Duration::from_secs(5);
        thread::sleep(time);
        phrase += 1;
    }
}
ndarilek commented 1 year ago

Please see the hello_world example for macOS-specific changes, specifically:

    // The below is only needed to make the example run on MacOS because there is no NSRunLoop in this context.
    // It shouldn't be needed in an app or game that almost certainly has one already.
    #[cfg(target_os = "macos")]
    {
        let run_loop: id = unsafe { NSRunLoop::currentRunLoop() };
        unsafe {
            let _: () = msg_send![run_loop, run];
        }
    }

Please re-open if that doesn't work, or submit a PR if you know how to integrate that into the library itself.

Thanks.

ninjaboy commented 1 year ago

Yeah, I tried that and it hangs indefinitely after the first phrase

#[cfg(target_os = "macos")]
use cocoa_foundation::base::id;
#[cfg(target_os = "macos")]
use cocoa_foundation::foundation::NSRunLoop;
#[cfg(target_os = "macos")]
use objc::{msg_send, sel, sel_impl};
use std::{thread, time};
use tts::*;

fn main() -> Result<(), Error> {
    // env_logger::init();
    let mut tts = Tts::default()?;
    let mut phrase = 1;
    loop {
        println!("{phrase}");
        tts.speak(format!("Phrase {}", phrase), false)?;
        let time = time::Duration::from_secs(5);
        thread::sleep(time);
        phrase += 1;
        #[cfg(target_os = "macos")]
        {
            let run_loop: id = unsafe { NSRunLoop::currentRunLoop() };
            unsafe {
                let _: () = msg_send![run_loop, run];
            }
        }
    }
}
ninjaboy commented 1 year ago

Sorry, I can't reopen this. Please let me know if the above integration of the quick fix is not correct. Thanks in advance

ndarilek commented 1 year ago

Sorry, not sure how to fix this and it works for what I need it to do under macOS.

I'll leave it open for folks with more macOS experience to help.

ninjaboy commented 1 year ago

Ok, I think what needs to be done is to call something like this:

let _: () = msg_send![run_loop, runMode:NSDefaultRunLoopMode beforeDate:NSDate::distantFuture()];

https://developer.apple.com/documentation/foundation/nsrunloop/1411525-runmode?language=objc

I am very new to Rust so, not sure of what to pass here: NSDate::distantFuture()

Any idea? I can try to proceed further :)

@ndarilek

ninjaboy commented 1 year ago

Ok, I got it working


#[cfg(target_os = "macos")]
use cocoa_foundation::base::id;
#[cfg(target_os = "macos")]
use cocoa_foundation::foundation::NSRunLoop;
use cocoa_foundation::foundation::{NSDate, NSDefaultRunLoopMode};
use objc::{
    class,
    runtime::{self, Object},
};
#[cfg(target_os = "macos")]
use objc::{msg_send, sel, sel_impl};
use std::{thread, time};
use tts::*;

fn main() -> Result<(), Error> {
    // env_logger::init();
    let mut tts = Tts::default()?;
    let mut phrase = 1;
    loop {
        println!("{phrase}");
        tts.speak(format!("Phrase {}", phrase), true)
            .expect("Failed");
        #[cfg(target_os = "macos")]
        {
            let run_loop: id = unsafe { NSRunLoop::currentRunLoop() };
            unsafe {
                let date: id = msg_send![class!(NSDate), distantFuture];
                let _: () = msg_send![run_loop, runMode:NSDefaultRunLoopMode beforeDate:date];
            }
        }

        let time = time::Duration::from_secs(1);
        thread::sleep(time);
        phrase += 1;
    }
}

Need to see how to include this call into the library after each call to speak.

ninjaboy commented 1 year ago

@ndarilek , https://github.com/ndarilek/tts-rs/pull/41 at least for an example

noxowl commented 1 year ago

I'm currently facing this problem. In a multithreaded environment (not sure if it matters), invoking TTS and then using the above solution will not work. Also, applying the above solution to the main thread will hang the main thread's loop. The expected behaviour is for on_utterance_end() to be called, but it is not.

But as far as I know, using opencv-rust's highgui works as expected without writing an NSRunLoop. This is probably because highgui already has code for cocoa_foundation.