Actionscript: How to handle events with same type but with different function listener? - ios

I'm attempting to make a sample application for ios using Actionscript (adobe air). But I'm having problems concerning events/event handling. My app basically gives the user the option to take a picture or select one from the camera roll to upload to a server. If the user decides to take a photo, I have to save that photo to the device's camera roll. The part of the code I'm having problem with is below:
private function readMediaData():void {
//set up some variables and data
var file:File = tempDir.resolvePath(filename);
var stream:FileStream = new FileStream();
stream.open(file, FileMode.WRITE);
stream.writeBytes(imageBytes);
stream.close();
file.addEventListener(Event.COMPLETE, uploadComplete, false, 0, true);
//upload file here
}
private function uploadComplete(event:Event):void {
//selectedImage is the MediaPromise
if (selectedImage.file == null) {
loader = new Loader();
loader.contentLoaderInfo.addEventListener(Event.COMPLETE, loaderCompleted);
loader.loadFilePromise(selectedImage);
}
}
private function loaderCompleted(event:Event):void {
//save image
}
The upload is working fine, but once the upload is completed, I get a somewhat infinite loop between loaderCompleted and uploadComplete resulting in multiple images being uploaded to the server. I tried removing the listener for the file once it has entered the uploadComplete function but still get the same result. My guess is that once the event listener for the loader is registered, it triggers an Event.COMPLETE notification which both method (or object) still recognizes. Is there a way to properly handle events with the same type but coming from different objects and with different function listeners?

Try setting the listener to the stream instead of to the file:
// Changed the 'file' with 'stream'
stream.addEventListener(Event.COMPLETE, uploadComplete, false, 0, true);

Related

RTCPeerConnection replaceTrack only changing stream for the remote peer

I am new to RTCPeerConnection (WEbRTC), so please bear with me.
So far I am able to get to the point where I can replace tracks on the run by switching camera or screen sharing in my app. But I noticed it in 2 browser tabs that newly replaced track stream is captured in partner/remote peer only, not on initiator's tab. It just keep showing old stream even though stream has been replaced.
It should've been nice if initiator can also see what he/she is sharing. I tried but no luck so far. Looking for some assistance.
My code looks like:
function screenShare(){
(async () => {
try {
await navigator.mediaDevices.getDisplayMedia(
{
cursor: true
}).then(stream => {
// localStream = stream;
let videoTrack = stream.getVideoTracks()[0];
var sender = senders.find(function(s) {
return s.track.kind == videoTrack.kind;
});
sender.replaceTrack(videoTrack);
videoTrack.onended = function(){
sender.replaceTrack(localStream.getTracks()[1]);
}
});
} catch (err) {
console.log('(async () =>: ' + err);
}
})();
}
Thanks in advance.
By design replaceTrack replaces the stream on the RTCPeerConnection. This does not affect the local video object. Reset the srcObject on the local video element to change it.

Electron Web Bluetooth API requestDevice() Error

I'm trying to develop an application which communicates with Bluetooth Low Energy Devices. I established a working "website" with the Web Bluetooth API. Everything works fine, so I used the Electron framework, to build an application.
The issue is known - if you start navigator.bluetooth.requestDevice(), you get this error message:
User cancelled the requestDevice() chooser..
This causes due to the missing device chooser in Chromium. There are several topics about workarounds I found, but no examples. This is my first Electron project. Maybe somebody solved this problem and can give me a hint :-)
thank you very much for your support. According to your suggestions and some research, I developed a working solution and like to share it with you.
These two links helped me a lot:
https://github.com/electron/electron/issues/11865
https://github.com/electron/electron/issues/10764
Especially this post from MarshallOfSound – well described:
Hook event in main process
Send message to renderer process with device list
Show UI in renderer process
Send selected device to main process
Call callback
To get more Information about the main process and renderer process, events and their API, read this:
https://www.electronjs.org/docs/tutorial/application-architecture#main-and-renderer-processes
https://www.electronjs.org/docs/api/ipc-main
https://www.electronjs.org/docs/api/web-contents#contentssendchannel-args
https://www.electronjs.org/docs/api/ipc-renderer
https://electronjs.org/docs/api/web-contents#event-select-bluetooth-device (already posted by Gerrit)
https://www.electronjs.org/docs/api/structures/bluetooth-device
For my application I wanted a device picker, as seen in Chrome. The sequence I wanted to implement is:
Start application
Search for a device
Devicepicker pops up
Select a device
Devicepicker closes
See data in application
A reference to the code for the processes of the tutorial and the code snippet:
electron application: main.js (main process) renderer.js (render process) devicepicker GUI: devicepicker.js (renderer process) devicepicker.html & layout.css (GUI)
1) Create devicepicker with a GUI (I used and two ) and a script
2) In your main.js create a select-bluetooth-device event inside the 'ready' event of your application object (docs in links above) When you start navigator.bluetooth.requestDevice() in your renderer.js, the event get’s fired and the devicelist is in the main process. With console.log(deviceList) it's visible in the shell. To handle it you need to send it to the renderer process (your application window).
3) To achieve this, we implement webContents.send of our BrowserWindow object inside the webContents.on event. Now the main process sends a devicelist every time he found new devices through the channel channelForBluetoothDeviceList
4) Create in renderer.js startDevicePicker(). devicePicker() must be started in the same function as navigator.bluetooth.requestDevice(). startDevicePicker() instantiates a new BrowserWindow() object which loads devicepicker.html
5) To get the list from the main process a ipcRenderer.on() listener must be implemented in startDevicePicker() that listens to the channelForBluetoothDeviceList channel of our main process. Now we can get the list in our electron application (renderer prcoess). To send it to the devicepicker UI, we need to forward it from our electron application (renderer process) to the devicepicker (also a renderer process)
6) To achieve this, we need the ipcRenderer.sendTo() sender in devicePicker(), which forwards messages between from a renderer process to a specific other renderer process. Additional to the channel bluetoothDeviceDiscoverList we need the BrowserWindow.id of the devicepicker. Since we just instantiated it, we can use our devicepicker object. I had a device which sended only once, the main process was faster than the build of the devicepicker and my list was never sent to the devicepicker. So I used a Promise() to wait with ipcRenderer.sendTo() until the devicepicker was ready to use.
7) To receive the devicelist at our devicepicker GUI, we need to listen to the bluetoothDeviceDiscoverList with ipcRenderer.on() (devicepicker.js). I inserted the devicelist now to the <option> of the devicepicker, you can use of course other elements (devicepicker.html). Please note: implement a query that compares the sended list to the current. Otherwise you get multiple devices and your selection gets loooong. I still need to do that, it's not finished yet :-)
8) To select a device that navigator.bluetooth.requestDevice() (renderer.js) gets resolved, we need to send back the BluetoothDevice.deviceId of our selected device to the main process where we callback ‚callback‘ with deviceId as callback parameter (main.js).
9) Now we can use ipcRenderer.sendTo() send the selected BluetoothDevice.deviceId to the mainprocess (devicepicker.js).
10) In the main process (main.js) of our electron application we listen to the channel channelForSelectingDevice with ipcMain.on() and callback with the received BluetoothDevice.deviceId. The device discovery gets stopped, navigator.bluetooth.requestDevice() gets resolved and we receive data from our device in our application (renderer.js). To cancel the discovery of devices, listen with ipcMain.on() in another channel channelForTerminationSignal just a signal to the main process (main.js), for example after a click (devicepicker.js) and call the callback with an empty string (as written in the docs)
I admit it could be done much simpler without a devicepicker. Then just send the devicelist from the main process (main.js) to your application (renderer process). But this helped me a lot to understand the processes in electron. I hope this tutorial is useful for you :-) !
Snippet:
main.js
const { ipcMain, app, BrowserWindow } = require('electron')
let win = null;
var callbackForBluetoothEvent = null;
// Create the browser window.
function createWindow () {
win = new BrowserWindow({
webPreferences: {
nodeIntegration: true //to activate require()
}
})
win.maximize()
win.show()
//This sender sends the devicelist from the main process to all renderer processes
win.webContents.on('select-bluetooth-device', (event, deviceList, callback) => {
event.preventDefault(); //important, otherwise first available device will be selected
console.log(deviceList); //if you want to see the devices in the shell
let bluetoothDeviceList = deviceList;
callbackForBluetoothEvent = callback; //to make it accessible outside createWindow()
win.webContents.send('channelForBluetoothDeviceList', bluetoothDeviceList);
});
// This method will be called when Electron has finished
// initialization and is ready to create browser windows.
// Some APIs can only be used after this event occurs.
app.on('ready', createWindow)
//cancels Discovery
ipcMain.on('channelForTerminationSignal', _ => {
callbackForBluetoothEvent(''); //reference to callback of win.webContents.on('select-bluetooth-device'...)
console.log("Discovery cancelled");
});
//resolves navigator.bluetooth.requestDevice() and stops device discovery
ipcMain.on('channelForSelectingDevice', (event, DeviceId) => {
callbackForBluetoothEvent(sentDeviceId); //reference to callback of win.webContents.on('select-bluetooth-device'...)
console.log("Device selected, discovery finished");
})
renderer.js
function discoverDevice() {
navigator.bluetooth.requestDevice()
startDevicepicker()
}
function startDevicepicker(){
let devicepicker = null;
let mainProcessDeviceList = null;
devicepicker = new BrowserWindow({
width: 350,
height: 270,
show: false, //needed to resolve promise devicepickerStarted()
webPreferences: {
nodeIntegration: true
}
})
devicepicker.loadFile('devicePicker.html');
//electron application listens for the devicelist from main process
ipcRenderer.on('channelForBluetoothDeviceList', (event, list) => {
mainProcessDeviceList = list;
devicepickerStarted.then(_=> {
console.log("Promise resolved!");
ipcRenderer.sendTo(devicepicker.webContents.id, 'bluetoothDeviceDiscoverList', mainProcessDeviceList);
})
})
//Promise that ensures that devicepicker GUI gets the list if the device only sends once
var devicepickerStarted = new Promise(
function (resolve, reject) {
console.log("Promise started");
devicepicker.once('ready-to-show', () => {
devicepicker.show();
resolve();
console.log("Devicepicker is ready!")
})
}
)
//remove listeners after closing devicepicker
devicepicker.on('closed', _ => {
devicepicker = null;
ipcRenderer.removeAllListeners('channelForBluetoothDeviceList');
ipcRenderer.removeAllListeners('currentWindowId');
ipcRenderer.removeAllListeners('receivedDeviceList');
})
}
devicepicker.js
//save received list here
var myDeviceList = new Array();
//Html elements
const devicePickerSelection = document.getElementById("devicePickerSelection");
const buttonSelect = document.getElementById("Select");
const buttonCancel = document.getElementById("Cancel");
//eventListeners for buttons
buttonSelect.addEventListener('click', selectFromDevicePicker);
buttonCancel.addEventListener('click', cancelDevicePicker);
//listens for deviceList
ipcRenderer.on('receivedDeviceList', (event, bluetoothDeviceDiscoverList) => {
console.log("list arrived!")
//code: add list to html element
});
function selectFromDevicePicker() {
let selectedDevice = devicePickerSelection.value;
let deviceId = //depends on where you save the BluetoothDevice.deviceId values
//sends deviceId to main process for callback to resolve navigator.bluetooth.requestDevice()
ipcRenderer.send('channelForSelectingDevice', deviceId);
ipcRenderer.removeAllListeners('receivedDeviceList');
closeDevicePicker();
}
function cancelDevicePicker() {
ipcRenderer.send('channelForTerminationSignal');
closeDevicePicker();
}
function closeDevicePicker() {
myDevicePicker.close();
}}
In your main.js Add this code snippet
if (process.platform === "linux"){
app.commandLine.appendSwitch("enable-experimental-web-platform-features", true);
} else {
app.commandLine.appendSwitch("enable-web-bluetooth", true);
}
This will enable the bluetooth at your Electron app.
And use this as reference
https://github.com/electron/electron/issues/11865
https://github.com/electron/electron/issues/7367
https://github.com/aalhaimi/electron-web-bluetooth
But I'd suggest you to consider your Electron version.
Here is a code sample that will just return the first device instead of having to implement a device chooser:
mainWindow.webContents.on('select-bluetooth-device', (event, deviceList, callback) => {
event.preventDefault();
console.log('Device list:', deviceList);
let result = deviceList[0];
if (!result) {
callback('');
} else {
callback(result.deviceId);
}
});
Source: https://electronjs.org/docs/api/web-contents#event-select-bluetooth-device

Web Audio API not playing sound sample on device, but works in browser

I have an Ionic app that is a metronome. Using Web Audio API I have made everything work using the oscillator feature, but when switching to use a wav file no audio is playing on a real device (iPhone).
When testing in the browser using Ionic Serve (chrome) the audio plays fine.
Here is what I have:
function snare(e) {
var audioSource = 'assets/audio/snare1.wav';
var request = new XMLHttpRequest();
request.open('GET', audioSource, true);
request.responseType = 'arraybuffer';
// Decode asynchronously
request.onload = function() {
audioContext.decodeAudioData(request.response, function(theBuffer) {
buffer = theBuffer;
playSound(buffer);
});
}
request.send();
}
function playSound(buffer) {
var source = audioContext.createBufferSource();
source.buffer = buffer;
source.connect(audioContext.destination);
source.start(0);
}
The audio sample is in www/assets/audio.
Any ideas where this could be going wrong?
I believe iOS devices require a user-gesture of some sort to allow playing of audio.
It's July 2017, iOS 10.3.2 and we're still finding this issue on Safari on iPhones. Interestingly Safari on a MacBook is fine.
#Raymond Toy's general observation still appears to be true. But #padenot's approach (via https://gist.github.com/laziel/7aefabe99ee57b16081c) did not work for me in a situation where I wanted to play a sound in response to some external event/trigger.
Using the original poster's code, I've had some success with this
var buffer; // added to make it work with OP's code
// keep the original function snare()
function playSound() { // dropped the argument for simplicity.
var source = audioContext.createBufferSource();
source.buffer = buffer;
source.connect(audioContext.destination);
source.start(0);
}
function triggerSound() {
function playSoundIos(event) {
document.removeEventListener('touchstart', playSoundIos);
playSound();
}
if (/iPad|iPhone/.test(navigator.userAgent)) {
document.addEventListener('touchstart', playSoundIos);
}
else { // Android etc. or Safari, but not on iPhone
playSound();
}
}
Now calling triggerSound() will produce the sound immediately on Android and will produce the sound on iOS after the browser page has been touched.
Still not ideal, but better than no sound at all...
I had a similar issue in current iOS (15). I tried to play base64 encoded binary data which worked in all browsers, but not on iOS.
Finally reordering of the statements solved my issue:
let buffer = Uint8Array.from(atob(base64), c => c.charCodeAt(0));
let context = new AudioContext();
// these lines were within "play()" before
audioSource = context.createBufferSource();
audioSource.connect(context.destination);
audioSource.start(0);
// ---
context.decodeAudioData(buffer.buffer, play, (e) => {
console.warn("error decoding audio", e)
});
function play(audioBuffer) {
audioSource.buffer = audioBuffer;
}
Also see this commit in my project.
I assume that calling audioSource.start(0) within the play() method was somehow too late because it's within a callback after context.decodeAudioData() and therefore maybe "too far away" from a user interaction for the standards of iOS.

Set timeout for notifications.notify FireFox Addon SDK

Please help me with Notification in my Firefox add-on.
var notifications = require("sdk/notifications");
function showNotifcation(title, text) {
notifications.notify({
iconURL: data.url("img/icon.png"),
title: title,
text: text
});
setTimeout(notifications.close(), 1000);
}
Not work.
Without more information from you it is not possible to be sure as to what your problem/issue is.
However, a brief look at the sdk/notifications documentation, and source code, indicates that you are attempting to use a non-existent method: notifications.close(). There is no such method in sdk/notifications.
One possible reason for your attempt to use this method is that you are conflating the Web Notification API, more detail, with the Add-on SDK sdk/notifications.
The Add-on SDK, sdk/notifications, has no way for you to programmatically close the notification from your code. Thus, there is no way for you to set a timeout for the notification using this interface. However, in some operating systems/windowing systems there is already a default timeout for these notifications.
You will need to either display a panel on your own, or use the chrome interfaces described in User Notifications and Alerts.
In addition, it would be unusual for you to be able to just call setTimeout(). That will, under most contexts, not be defined. You would normally need to use sdk/timers with:
var { setTimeout } = require("sdk/timers");
In some contexts, you might be able to use window.setTimeout(), when window is appropriately defined (which you will probably have to set yourself).
Modifying the code from my answer to Prevent XUL notificationBox from closing when button is hit (if you want buttons, that answer will show you how to do it), and other answers of mine: Something along the lines of what I believe you desire would be (code for the timeout is at the bottom):
function showNotificationBox(text) {
//Create some common variables if they do not exist.
if (window === null || typeof window !== "object") {
// Add/remove a "/" to comment/un-comment the code appropriate for your add-on:
//* Add-on SDK:
var window = require('sdk/window/utils').getMostRecentBrowserWindow();
//*/
/* Overlay and bootstrap (from almost any context/scope):
var window=Components.classes["#mozilla.org/appshell/window-mediator;1"]
.getService(Components.interfaces.nsIWindowMediator)
.getMostRecentWindow("navigator:browser");
//*/
}
if (typeof gBrowser === "undefined") {
var gBrowser = window.gBrowser;
}
let notifyBox = gBrowser.getNotificationBox();
//appendNotification( label , value , image (URL) , priority , buttons, eventCallback )
let theNotification = notifyBox.appendNotification(text, "Test notification unique ID",
"chrome://browser/content/aboutRobots-icon.png",
notifyBox.PRIORITY_INFO_HIGH, [], null);
//* Add-on SDK:
var { setTimeout } = require("sdk/timers");
setTimeout(theNotification.close(), 10000);
//*/
/* Overlay and bootstrap:
let timerCallback = {
notify:function notify() {theNotification.close(); }
}
let closeNotificationTimer = Components.classes["#mozilla.org/timer;1"]
.createInstance(Components.interfaces.nsITimer);
closeNotificationTimer.initWithCallback(timerCallback,10000,
Components.interfaces.nsITimer.TYPE_ONE_SHOT);
//*/
}
Note: I changed the timeout to 10 seconds from the 1 second which is in the code in your question. One second is a unreasonable amount of time to expect to show anything which you actually desire the user to see and understand.
The above implements the user notification in a notificationBox. As such it shows up within the Firefox window:
It is also possible to use the nsIAlertsService which is what sdk/notifications uses. This will normally display an alert box in the bottom right of the screen, potentially outside of the Firefox window (see image on nsIAlertsService for example). The notification may show up elsewhere depending on how you have your windowing system set up (this is OS dependent). However, the documentation did not have a method to clear the notification, or set a timeout. However, the interface definition does show that a closeAlert() method does exist. The source code for the sdk/notifications does not expose this to the Add-on SDK. Thus, you would need to use the chrome interfaces. I have updated the documentation to show closeAlert().
Such as (some code taken and modified from nsIAlertsService):
//* Add-on SDK:
var {Cc, Ci} = require("chrome");
//*/
/* Overlay and bootstrap:
const Cc = Components.classes;
const Ci = Components.interfaces;
//*/
function showNotifcation(title, text) {
var alertsService = Cc["#mozilla.org/alerts-service;1"].getService(Ci.nsIAlertsService);
try {
//The second use of title is the alert name.
alertsService.showAlertNotification(icon, title, text, false, "", null, title);
} catch (e) {
// This can fail on Mac OS X
}
//* Add-on SDK:
var { setTimeout } = require("sdk/timers");
setTimeout(alertsService.closeAlert(title), 10000);
//*/
/* Overlay and bootstrap:
let alertTimerCallback = {
notify:function notify() {alertsService.closeAlert(title); }
}
let closeAlertTimer = Cc["#mozilla.org/timer;1"].createInstance(Components.interfaces
.nsITimer);
closeAlertTimer.initWithCallback(alertTimerCallback,10000,Ci.nsITimer.TYPE_ONE_SHOT);
//*/
}
I have only tested the above code with a bootstrapped/restartless Firefox add-on. Thus, the Add-on SDK code may be slightly off.

xpcom/jetpack observe all document loads

I write a Mozilla Jetpack based add-on that has to run whenever a document is loaded. For "toplevel documents" this mostly works using this code (OserverService = require('observer-service')):
this.endDocumentLoadCallback = function (subject, data) {
console.log('loaded: '+subject.location);
try {
server.onEndDocumentLoad(subject);
}
catch (e) {
console.error(formatTraceback(e));
}
};
ObserverService.add("EndDocumentLoad", this.endDocumentLoadCallback);
But the callback doesn't get called when the user opens a new tab using middle click or (more importantly!) for frames. And even this topic I only got through reading the source of another extension and not through the documentation.
So how do I register a callback that really gets called every time a document is loaded?
Edit: This seems to do what I want:
function callback (event) {
// this is the content document of the loaded page.
var doc = event.originalTarget;
if (doc instanceof Ci.nsIDOMNSHTMLDocument) {
// is this an inner frame?
if (doc.defaultView.frameElement) {
// Frame within a tab was loaded.
console.log('!!! loaded frame:',doc.location.href);
}
else {
console.log('!!! loaded top level document:',doc.location.href);
}
}
}
var wm = Cc["#mozilla.org/appshell/window-mediator;1"].getService(Ci.nsIWindowMediator);
var mainWindow = wm.getMostRecentWindow("navigator:browser");
mainWindow.gBrowser.addEventListener("load", callback, true);
Got it partially from here: https://developer.mozilla.org/en/XUL_School/Intercepting_Page_Loads
#kizzx2 you are better served with #jetpack
To the original question: why don't you use tab-browser module. Something like this:
var browser = require("tab-browser");
exports.main = function main(options, callbacks) {
initialize(function (config) {
browser.whenContentLoaded(
function(window) {
// something to do with the window
// e.g., if (window.locations.href === "something")
}
);
});
Much cleaner than what you do IMHO and (until we have official pageMods module) the supported way how to do this.
As of Addon SDK 1.0, the proper way to do this is to use the page-mod module.
(Under the hood it's implemented using the document-element-inserted observer service notification, you can use it in a regular extension or if page-mod doesn't suit you.)

Resources