Swift: command center not working, even though I explicitly set buttons to enable? - audio

Ok, having some problems with Apple's command center here, playing background audio/on lock screen and cant understand why. Seems pretty simple but I don't even have the episode information displaying reliably in command center, definitely cant play/pause.
First I start the Audio session:
do {
try AVAudioSession.sharedInstance().setCategory(AVAudioSessionCategoryPlayback, with: .mixWithOthers)
print("Playback OK")
try AVAudioSession.sharedInstance().setActive(true)
print("Session is Active")
} catch {
print(error)
}
then I set up my command center buttons to be enabled explicitly:
UIApplication.shared.beginReceivingRemoteControlEvents()
let commandCenter = MPRemoteCommandCenter.shared()
commandCenter.skipForwardCommand.isEnabled = true
commandCenter.skipBackwardCommand.isEnabled = true
commandCenter.nextTrackCommand.isEnabled = true
commandCenter.togglePlayPauseCommand.isEnabled = true
// commandCenter.previousTrackCommand.isEnabled = true
// commandCenter.togglePlayPauseCommand.isEnabled = true
commandCenter.togglePlayPauseCommand.addTarget(self, action:#selector(togglePlayPause))
// commandCenter.nextTrackCommand.addTarget(self, action:#selector(nextTrackForward))
// commandCenter.previousTrackCommand.addTarget(self, action:#selector(nextTrackBack))
commandCenter.skipForwardCommand.addTarget(self, action:#selector(ffPressed))
commandCenter.skipBackwardCommand.addTarget(self, action:#selector(rwPressed))
Here is the lldb plist keys if that matters for audio:
What's wrong here?

Maybe your question is old but someone could need this too, so this is how I did it :
Swift 5
let player = AVPlayer()
func commandCenter () {
let commandCenter = MPRemoteCommandCenter.shared()
commandCenter.playCommand.addTarget { (commandEvent) -> MPRemoteCommandHandlerStatus in self.play(); return .success }
commandCenter.pauseCommand.addTarget { (commandEvent) -> MPRemoteCommandHandlerStatus in self.pause(); return .success }
}
func pause () {
player.pause ()
print("paused")
}
func play () {
player.play()
print("play")
}
You don't need to enable commandCenter.playCommand to true

Related

Can't use touch gestures even with custom FabricJS builds

I've been trying to get a FabricJs canvas work with multitouch pan and zoom, but to no avail. I've tried countless custom builds but the event doesn't have any touch information to work on. Here's the code I use:
let fabricCanvas = new fabric.Canvas('myCanvas', {
width: canvasContainer.current.offsetWidth,
height: canvasContainer.current.offsetHeight,
isDrawingMode: true
})
fabricCanvas.on({
'touch:gesture': function(e) {
console.log(e) // returns empty object wen fired with fabricCanvas.fire("touch:gesture")
}
});
fabricCanvas.fire("touch:gesture") // I can only make the listener fire, by doing this
How can I make the gestures provided work normally?
If you look at the library code, there's a line preventing gesture event being fired when isDrawingMode is true.
in /src/mixins/canvas_gestures.mixin.js
__onTransformGesture: function(e, self) {
if (this.isDrawingMode || !e.touches || e.touches.length !== 2 || 'gesture' !== self.gesture) {
return;
}
var target = this.findTarget(e);
if ('undefined' !== typeof target) {
this.__gesturesParams = {
e: e,
self: self,
target: target
};
this.__gesturesRenderer();
}
this.fire('touch:gesture', {
target: target, e: e, self: self
});
},
I'm also trying to use gesture with drawingMode and don't know why it prevents gesture on drawingMode.
Currently, I'm trying to use a custom build with modified source code.
Or you can try not to use isDrawingMode and use mouse events to implement freedraw

How to switch screens in a rust tui app (termion + tui-rs)

I have a tui app where a user is presented with some choices through a list. Once they navigate to the choice they want and hit enter I'd like to take them to the "next" screen.
It's more complicated than just clearning existing text and printing new one because I also need to replace keybindings and basically start a new tui-rs loop. More below.
Code for Screen 1:
pub fn draw_screen() -> Result<(), Box<dyn Error>> {
// Terminal initialization
let stdout = io::stdout().into_raw_mode()?;
let stdout = MouseTerminal::from(stdout);
let stdout = AlternateScreen::from(stdout);
let backend = TermionBackend::new(stdout);
let mut terminal = Terminal::new(backend)?;
let events = Events::new();
loop {
terminal.draw(|f| {
// user shown a list they can navigate through using arrow keys
});
match events.next()? {
Event::Input(input) => match input {
Key::Char('q') => {
break;
}
Key::Char('\n') => {
// this is where I need to "send" them to a new screen
}
Key::Down => {
// my_list won't exist on next screen
my_list.items.next();
}
Key::Up => {
my_list.items.previous();
}
_ => {}
},
_ => {}
}
}
Ok(())
}
As can be seen the keybindings at the bottom are specific to this screen. Eg on the next screen there's not going to be a my_list and instead there might be a my_another_list or my_box or nothing at all.
So if all I did was clear the text, I'd still be left inside the same loop with the same keybindings - doesn't work.
What's the right way to initiate a new loop with fresh keybindings?

wbkgd does not set background color

I'm using Rust to write an ncurses app.
I'm trying to set the color of a subwin, however having no success. I'm not even sure the window is created in the first place, or it just doesn't want to set the color.
Here's a minimal example:
use ncurses::*;
fn main() {
setlocale(LcCategory::all, "");
initscr();
keypad(stdscr(), true);
start_color();
init_pair(1, COLOR_RED, COLOR_RED);
loop {
let user_input = get_wch();
match user_input.unwrap() {
WchResult::Char(ch) => {
match ch {
27 => break,
_ => {}
}
},
WchResult::KeyCode(code) => {
match code {
KEY_F5 => {
let ln = subwin(stdscr(), LINES(), 5, 0, 0);
wbkgd(ln, COLOR_PAIR(1));
refresh();
},
_ => {}
}
}
}
}
endwin();
}
As you can see, I initialized a color pair and invoked start_colors().
What could be the issue?
I think the problem might be that your not refreshing the sub-window. Try using wrefresh(ln) instead. Actually use both refresh and refresh(ln).
In this chunk
let ln = subwin(stdscr(), LINES(), 5, 0, 0);
wbkgd(ln, COLOR_PAIR(1));
refresh();
the refresh overwrites the result from the subwin. Also, you would get better results by ORing the COLOR_PAIR with a space (see this).
Addressing the comments:
let user_input = get_wch();
also does a refresh (overwriting the result from the subwin).

Temporarily suspend user from making another request

I'm building a swift iOS-based app that limits users to submit only one request per 10 minutes.I'm suck now translating these three points to code
1- user clicks a button
2- button is disabled for 10 minute
3- button is enabled
I'm not expecting the full code :) just a method or advice.
Thank you
First you need to keep the state in persistent data, incase user go to another controller and go back, the button should still disabled.
class PersistentData {
static let sharedInstance = PersistentData();
var disableSubmitButton = false
}
Then in your controller
override func viewDidLoad() {
// each load need to check
if PersistentData.sharedInstance.disableSubmitButton == true {
submitButton.isEnabled = false
}
}
func onButtonClicked() {
// change button to disable
submitButton.isEnabled = false
// set state in persistent data so it can be the same wherever controller you go
PersistentData.sharedInstance.disableSubmitButton = true
// now set the timer to enable back
Timer.scheduledTimer(timeInterval: 10.0 * 60, target: self, selector: #selector(self.updateButtonState), userInfo: nil, repeats: false);
}
func updateButtonState() {
// update value in persitence data
PersistentData.sharedInstance.disableSubmitButton = false
// change button to enable back
submitButton.isEnabled = true
}
Note the following: timeInterval is in seconds so 60*10 is 10 minutes. UI Updates need to happen on the main thread which is why the time block gets dispatched back to main.
#IBAction func touchButton(_ sender: AnyObject) {
button.isEnabled = false
Timer.scheduledTimer(withTimeInterval: 60*10, repeats: false) { _ in
DispatchQueue.main.async {
self.button.isEnabled = true
}
}
}

NSSpeechSynthesizer Saving to URL

I'm creating a MacOS application that generates dialog files from a tab-delimited text file. The text file is formatted (output filename) [tab] (text to be spoken to that file). I got the program working perfectly running in the main thread, but this blocked the UI, of course. When I moved the meat of the application to a User Initiated thread, I unblocked the UI, but now it occasionally and randomly speaks a line out the speaker instead of to a file. A file is still created, but it's of zero duration. If the script contains 1,000 lines, it might get through the whole thing perfectly, or it might "speak" ten or more of them out the speaker. It's almost always different lines each time.
This is the code that speaks to a file:
// Set URL to save file to disk
if let speechFileURL = NSURL(string: speechFilePath) {
speechSynth.startSpeakingString(spokenText, toURL: speechFileURL) // Speak to file
}
else {
dialogAlert("Invalid File: ", text: speechFileName)
}
while(NSSpeechSynthesizer.isAnyApplicationSpeaking() == true) {
if self.gUserClickedStop {
speechSynth.stopSpeaking()
self.gIsSpeaking = false
break
}
}
This is the call to the function that parses the script and generates the speech files:
dispatch_async(dispatch_get_global_queue(QOS_CLASS_USER_INITIATED, 0)) { [unowned self] in
self.parseTheScript()
}
As mentioned above, calling parseTheScript() without wrapping it in the Grand Central Dispatch call makes it work perfectly, except for the blocking issue.
I'm new to programming in Swift and the idea of multithreaded programming, so I'm sure I'm doing something wrong, but I'm stumped as to how to fix this problem.
Thank you in advance for your help.
Edit to add...
This is the entire function that parses the text file and generates the dialog files:
// This opens the text file and generates the spoken audio files
func parseTheScript() {
let filemgr = NSFileManager.defaultManager()
print("Chosen path: \(scriptFileTextField.stringValue)") // debug
if filemgr.fileExistsAtPath(scriptFileTextField.stringValue) {
print("File exists")
do {
outputFilename.stringValue = ""
scriptToSpeak.stringValue = ""
outputFilename.hidden = false
scriptToSpeak.hidden = false
progressBar.minValue = 0
progressBar.doubleValue = 0.0
progressBar.hidden = false
filenameLabel.hidden = false
let text = try String(contentsOfFile: scriptFileTextField.stringValue, encoding: NSUTF8StringEncoding)
let lines: [String] = text.componentsSeparatedByString("\n")
var progressEndValue = Double(lines.count)
progressBar.maxValue = progressEndValue
let speechSynth = NSSpeechSynthesizer.init()
let theVoice = gVoiceList[voiceListPopup.indexOfSelectedItem - 1] // Not sure why it's off by one
speechSynth.setVoice(theVoice)
for line in lines {
let thisLine: [String] = line.componentsSeparatedByString("\t") // Split the line at the tab character
if (thisLine.count == 2) { // Did the line split into two parts?
let speechFileName = thisLine[0]
let spokenText = thisLine[1]
outputFilename.stringValue = speechFileName
scriptToSpeak.stringValue = spokenText
let speechFilePath = destinationFolderTextField.stringValue + speechFileName + ".aif" // Build the path string for the output speech file
print("Filename: \(speechFilePath)")
print("SpokenText: \(spokenText)")
if(gSpeakToFile) {
// Set URL to save file to disk
if let speechFileURL = NSURL(string: speechFilePath) {
speechSynth.startSpeakingString(spokenText, toURL: speechFileURL) // Speak to file
}
else {
dialogAlert("Invalid File: ", text: speechFileName)
}
}
else {
speechSynth.startSpeakingString(spokenText) // Speak to audio output
}
while(NSSpeechSynthesizer.isAnyApplicationSpeaking() == true) {
if self.gUserClickedStop {
speechSynth.stopSpeaking()
self.gIsSpeaking = false
break
}
}
progressBar.incrementBy(1.0)
}
if gUserClickedStop {
gUserClickedStop = false
gIsSpeaking = false
progressEndValue = 0
generateButton.title = "Start"
break
}
}
gIsSpeaking = false
progressBar.doubleValue = progressEndValue
generateButton.title = "Start"
filenameLabel.hidden = true
outputFilename.stringValue = ""
scriptToSpeak.stringValue = ""
} catch let error as NSError {
dialogAlert("Error:", text: String(error.localizedDescription))
print("Error: \(error)")
}
} else {
print("File does not exist")
}
}

Resources