зеркало из https://github.com/mozilla/pluotsorbet.git
Merge branch 'master' into fs-perf
This commit is contained in:
Коммит
1706100a89
133
index.js
133
index.js
|
@ -36,10 +36,6 @@ var DumbPipe = {
|
|||
// Functions that receive messages from the other side for active pipes.
|
||||
recipients: {},
|
||||
|
||||
// Queue of messages to send to the other side. Retrieved by the other side
|
||||
// via a "get" message.
|
||||
outgoingMessages: [],
|
||||
|
||||
// Every time we want to make the other side retrieve messages, the hash
|
||||
// of the other side's web page has to change, so we increment it.
|
||||
nextHashID: 0,
|
||||
|
@ -76,9 +72,6 @@ var DumbPipe = {
|
|||
//console.log("outer recv: " + JSON.stringify(envelope));
|
||||
this.receiveMessage(envelope.pipeID, envelope.message);
|
||||
break;
|
||||
case "get":
|
||||
this.getMessages(event);
|
||||
break;
|
||||
case "close":
|
||||
//console.log("outer recv: " + JSON.stringify(envelope));
|
||||
this.closePipe(envelope.pipeID);
|
||||
|
@ -109,12 +102,12 @@ var DumbPipe = {
|
|||
// Oh my shod, that's some funky git!
|
||||
var envelope = { pipeID: pipeID, message: message };
|
||||
//console.log("outer send: " + JSON.stringify(envelope));
|
||||
this.outgoingMessages.push(envelope);
|
||||
|
||||
var mozbrowser = document.getElementById("mozbrowser");
|
||||
window.setZeroTimeout(function() {
|
||||
mozbrowser.src = mozbrowser.src.split("#")[0] + "#" + this.nextHashID++;
|
||||
}.bind(this));
|
||||
try {
|
||||
document.getElementById("mozbrowser").contentWindow.postMessage(envelope, "*");
|
||||
} catch (e) {
|
||||
console.log("Error " + e + " while sending message: " + JSON.stringify(message));
|
||||
}
|
||||
},
|
||||
|
||||
receiveMessage: function(pipeID, message, detail) {
|
||||
|
@ -132,17 +125,6 @@ var DumbPipe = {
|
|||
}.bind(this));
|
||||
},
|
||||
|
||||
getMessages: function(event) {
|
||||
try {
|
||||
event.detail.returnValue = JSON.stringify(this.outgoingMessages);
|
||||
} catch(ex) {
|
||||
console.error("failed to stringify outgoing messages: " + ex);
|
||||
} finally {
|
||||
this.outgoingMessages = [];
|
||||
event.detail.unblock();
|
||||
}
|
||||
},
|
||||
|
||||
closePipe: function(pipeID) {
|
||||
delete this.recipients[pipeID];
|
||||
}
|
||||
|
@ -226,11 +208,7 @@ DumbPipe.registerOpener("socket", function(message, sender) {
|
|||
}
|
||||
|
||||
socket.ondata = function(event) {
|
||||
// Turn the buffer into a regular Array to traverse the mozbrowser boundary.
|
||||
var array = Array.prototype.slice.call(new Uint8Array(event.data));
|
||||
array.constructor = Array;
|
||||
|
||||
sender({ type: "data", data: array });
|
||||
sender({ type: "data", data: event.data });
|
||||
}
|
||||
|
||||
socket.ondrain = function(event) {
|
||||
|
@ -264,3 +242,102 @@ DumbPipe.registerOpener("socket", function(message, sender) {
|
|||
}
|
||||
};
|
||||
});
|
||||
|
||||
DumbPipe.registerOpener("audiorecorder", function(message, sender) {
|
||||
var mediaRecorder = null;
|
||||
var localAudioStream = null;
|
||||
|
||||
function startRecording(localStream) {
|
||||
localAudioStream = localStream;
|
||||
|
||||
mediaRecorder = new MediaRecorder(localStream, {
|
||||
mimeType: message.mimeType // 'audio/3gpp' // need to be certified app.
|
||||
});
|
||||
|
||||
mediaRecorder.ondataavailable = function(e) {
|
||||
if (e.data.size == 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
var fileReader = new FileReader();
|
||||
fileReader.onload = function() {
|
||||
sender({ type: "data", data: fileReader.result });
|
||||
};
|
||||
fileReader.readAsArrayBuffer(e.data);
|
||||
};
|
||||
|
||||
mediaRecorder.onstop = function(e) {
|
||||
// Do nothing here, just relay the event.
|
||||
//
|
||||
// We can't close the pipe here, one reason is |onstop| is fired before |ondataavailable|,
|
||||
// if close pipe here, there is no chance to deliever the recorded voice. Another reason is
|
||||
// the recording might be stopped and started back and forth. So let's do the pipe
|
||||
// closing on the other side instead, i.e. DirectRecord::nClose.
|
||||
sender({ type: "stop" });
|
||||
};
|
||||
|
||||
mediaRecorder.onerror = function(e) {
|
||||
sender({ type: "error" });
|
||||
};
|
||||
|
||||
mediaRecorder.onpause = function(e) {
|
||||
sender({ type: "pause" });
|
||||
};
|
||||
|
||||
mediaRecorder.onstart = function(e) {
|
||||
sender({ type: "start" });
|
||||
};
|
||||
|
||||
mediaRecorder.start();
|
||||
}
|
||||
|
||||
return function(message) {
|
||||
switch(message.type) {
|
||||
case "start":
|
||||
try {
|
||||
if (!mediaRecorder) {
|
||||
navigator.mozGetUserMedia({
|
||||
audio: true
|
||||
}, function(localStream) {
|
||||
startRecording(localStream);
|
||||
}, function(e) {
|
||||
sender({ type: "error", error: e });
|
||||
});
|
||||
} else if (mediaRecorder.state == "paused") {
|
||||
mediaRecorder.resume();
|
||||
} else {
|
||||
mediaRecorder.start();
|
||||
}
|
||||
} catch (e) {
|
||||
sender({ type: "error", error: e });
|
||||
}
|
||||
break;
|
||||
case "requestData":
|
||||
try {
|
||||
// An InvalidState error might be thrown.
|
||||
mediaRecorder.requestData();
|
||||
} catch (e) {
|
||||
sender({ type: "error", error: e });
|
||||
}
|
||||
break;
|
||||
case "pause":
|
||||
try {
|
||||
mediaRecorder.pause();
|
||||
} catch (e) {
|
||||
sender({ type: "error", error: e });
|
||||
}
|
||||
break;
|
||||
case "stop":
|
||||
try {
|
||||
mediaRecorder.stop();
|
||||
localAudioStream.stop();
|
||||
mediaRecorder = null;
|
||||
localAudioStream = null;
|
||||
} catch (e) {
|
||||
sender({ type: "error", error: e });
|
||||
}
|
||||
break;
|
||||
}
|
||||
};
|
||||
});
|
||||
|
||||
|
|
18
libs/fs.js
18
libs/fs.js
|
@ -291,6 +291,24 @@ var fs = (function() {
|
|||
}
|
||||
}
|
||||
|
||||
function flushAll() {
|
||||
for (var fd = 0; fd < openedFiles.length; fd++) {
|
||||
if (!openedFiles[fd] || !openedFiles[fd].dirty) {
|
||||
continue;
|
||||
}
|
||||
flush(fd);
|
||||
}
|
||||
}
|
||||
|
||||
// Due to bug #227, we don't support Object::finalize(). But the Java
|
||||
// filesystem implementation requires the `finalize` method to save cached
|
||||
// file data if user doesn't flush or close the file explicitly. To avoid
|
||||
// losing data, we flush files periodically.
|
||||
setInterval(flushAll, 5000);
|
||||
|
||||
// Flush files when app goes into background.
|
||||
window.addEventListener("pagehide", flushAll);
|
||||
|
||||
function list(path, cb) {
|
||||
path = normalizePath(path);
|
||||
if (DEBUG_FS) { console.log("fs list " + path); }
|
||||
|
|
51
libs/pipe.js
51
libs/pipe.js
|
@ -78,46 +78,27 @@ var DumbPipe = {
|
|||
}
|
||||
},
|
||||
|
||||
handleEvent: function(event) {
|
||||
// To ensure we don't fill up the browser history over time, we navigate
|
||||
// "back" every time the other side navigates us "forward" by changing
|
||||
// the hash. This will trigger a second hashchange event; to avoid getting
|
||||
// messages twice, we only get them for the second hashchange event,
|
||||
// i.e. once we've returned to the hashless page, at which point a second
|
||||
// call to window.history.back() will have had no effect.
|
||||
//
|
||||
// We only do this when we're in mozbrowser (i.e. window.parent === window),
|
||||
// since window.history.back() affects the parent window otherwise.
|
||||
//
|
||||
if (window.parent === window) {
|
||||
var hash = window.location.hash;
|
||||
window.history.back();
|
||||
if (window.location.hash != hash) {
|
||||
return;
|
||||
}
|
||||
receiveMessage: function(event) {
|
||||
if (event.source === window) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.send({ command: "get" }, function(envelopes) {
|
||||
envelopes.forEach((function(envelope) {
|
||||
//console.log("inner recv: " + JSON.stringify(envelope));
|
||||
window.setZeroTimeout(function() {
|
||||
if (this.recipients[envelope.pipeID]) {
|
||||
try {
|
||||
this.recipients[envelope.pipeID](envelope.message);
|
||||
} catch(ex) {
|
||||
console.error(ex + "\n" + ex.stack);
|
||||
}
|
||||
} else {
|
||||
console.warn("nonexistent pipe " + envelope.pipeID + " received message " +
|
||||
JSON.stringify(envelope.message));
|
||||
}
|
||||
}.bind(this));
|
||||
}).bind(this));
|
||||
}.bind(this));
|
||||
var envelope = event.data;
|
||||
|
||||
if (this.recipients[envelope.pipeID]) {
|
||||
try {
|
||||
this.recipients[envelope.pipeID](envelope.message);
|
||||
} catch(ex) {
|
||||
console.error(ex + "\n" + ex.stack);
|
||||
}
|
||||
} else {
|
||||
console.warn("nonexistent pipe " + envelope.pipeID + " received message " +
|
||||
JSON.stringify(envelope.message));
|
||||
}
|
||||
},
|
||||
};
|
||||
|
||||
window.addEventListener("hashchange", DumbPipe.handleEvent.bind(DumbPipe), false);
|
||||
window.addEventListener("message", DumbPipe.receiveMessage.bind(DumbPipe), false);
|
||||
|
||||
// If "mozbrowser" isn't enabled on the frame we're loaded in, then override
|
||||
// the alert/prompt functions to funnel messages to the endpoint in the parent.
|
||||
|
|
|
@ -20,6 +20,7 @@
|
|||
* midletClassName
|
||||
* network_mcc
|
||||
* network_mnc
|
||||
* platform
|
||||
* profile
|
||||
* pushConn
|
||||
* pushMidlet
|
||||
|
|
|
@ -23,7 +23,10 @@
|
|||
"mobilenetwork": {
|
||||
"description:": "Required to verify your phone number"
|
||||
},
|
||||
"browser": {}
|
||||
"browser": {},
|
||||
"audio-capture": {
|
||||
"description": "Required to capture audio via getUserMedia"
|
||||
}
|
||||
},
|
||||
"type": "privileged"
|
||||
}
|
||||
|
|
234
midp/media.js
234
midp/media.js
|
@ -10,6 +10,7 @@ Media.ContentTypes = {
|
|||
],
|
||||
|
||||
file: [
|
||||
"audio/ogg",
|
||||
"audio/x-wav",
|
||||
"audio/mpeg",
|
||||
"image/jpeg",
|
||||
|
@ -68,6 +69,7 @@ Media.extToFormat = new Map([
|
|||
]);
|
||||
|
||||
Media.contentTypeToFormat = new Map([
|
||||
["audio/ogg", "ogg"],
|
||||
["audio/amr", "amr"],
|
||||
["audio/x-wav", "wav"],
|
||||
["audio/mpeg", "MPEG_layer_3"],
|
||||
|
@ -75,7 +77,7 @@ Media.contentTypeToFormat = new Map([
|
|||
["image/png", "PNG"],
|
||||
]);
|
||||
|
||||
Media.supportedAudioFormats = ["MPEG_layer_3", "wav", "amr"];
|
||||
Media.supportedAudioFormats = ["MPEG_layer_3", "wav", "amr", "ogg"];
|
||||
Media.supportedImageFormats = ["JPEG", "PNG"];
|
||||
|
||||
Native.create("com/sun/mmedia/DefaultConfiguration.nListContentTypesOpen.(Ljava/lang/String;)I", function(jProtocol) {
|
||||
|
@ -406,7 +408,25 @@ function PlayerContainer(url) {
|
|||
// default buffer size 1 MB
|
||||
PlayerContainer.DEFAULT_BUFFER_SIZE = 1024 * 1024;
|
||||
|
||||
PlayerContainer.prototype.isAudioCapture = function() {
|
||||
return !!(this.url && this.url.startsWith("capture://audio"));
|
||||
};
|
||||
|
||||
PlayerContainer.prototype.guessFormatFromURL = function() {
|
||||
if (this.isAudioCapture()) {
|
||||
var encoding = "audio/ogg"; // Same as system property |audio.encodings|
|
||||
|
||||
var idx = this.url.indexOf("encoding=");
|
||||
if (idx > 0) {
|
||||
var encodingKeyPair = this.url.substring(idx).split("&")[0].split("=");
|
||||
encoding = encodingKeyPair.length == 2 ? encodingKeyPair[1] : encoding;
|
||||
}
|
||||
|
||||
var format = Media.contentTypeToFormat.get(encoding);
|
||||
|
||||
return format || "UNKNOWN";
|
||||
}
|
||||
|
||||
return Media.extToFormat.get(this.url.substr(this.url.lastIndexOf(".") + 1)) || "UNKNOWN";
|
||||
}
|
||||
|
||||
|
@ -424,6 +444,9 @@ PlayerContainer.prototype.realize = function(contentType) {
|
|||
|
||||
if (Media.supportedAudioFormats.indexOf(this.mediaFormat) !== -1) {
|
||||
this.player = new AudioPlayer(this);
|
||||
if (this.isAudioCapture()) {
|
||||
this.audioRecorder = new AudioRecorder();
|
||||
}
|
||||
this.player.realize().then(resolve);
|
||||
} else if (Media.supportedImageFormats.indexOf(this.mediaFormat) !== -1) {
|
||||
this.player = new ImagePlayer(this);
|
||||
|
@ -476,6 +499,11 @@ PlayerContainer.prototype.getMediaFormat = function() {
|
|||
return "mid";
|
||||
}
|
||||
|
||||
// https://wiki.xiph.org/Ogg#Detecting_Ogg_files_and_extracting_information
|
||||
if (headerString.indexOf("OggS") === 0) {
|
||||
return "ogg";
|
||||
}
|
||||
|
||||
return this.mediaFormat;
|
||||
};
|
||||
|
||||
|
@ -553,6 +581,155 @@ PlayerContainer.prototype.setVisible = function(visible) {
|
|||
this.player.setVisible(visible);
|
||||
}
|
||||
|
||||
PlayerContainer.prototype.getRecordedSize = function() {
|
||||
return this.audioRecorder.data.byteLength;
|
||||
};
|
||||
|
||||
PlayerContainer.prototype.getRecordedData = function(offset, size, buffer) {
|
||||
var toRead = (size < this.audioRecorder.data.length) ? size : this.audioRecorder.data.byteLength;
|
||||
buffer.set(this.audioRecorder.data.subarray(0, toRead), offset);
|
||||
this.audioRecorder.data = new Uint8Array(this.audioRecorder.data.buffer.slice(toRead));
|
||||
};
|
||||
|
||||
var AudioRecorder = function(aMimeType) {
|
||||
this.mimeType = aMimeType || "audio/ogg";
|
||||
this.eventListeners = {};
|
||||
this.data = new Uint8Array();
|
||||
this.sender = DumbPipe.open("audiorecorder", {
|
||||
mimeType: this.mimeType
|
||||
}, this.recipient.bind(this));
|
||||
};
|
||||
|
||||
AudioRecorder.prototype.recipient = function(message) {
|
||||
var callback = this["on" + message.type];
|
||||
if (typeof callback === "function") {
|
||||
callback(message);
|
||||
}
|
||||
|
||||
if (this.eventListeners[message.type]) {
|
||||
this.eventListeners[message.type].forEach(function(listener) {
|
||||
if (typeof listener === "function") {
|
||||
listener(message);
|
||||
}
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
AudioRecorder.prototype.addEventListener = function(name, callback) {
|
||||
if (!callback || !name) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!this.eventListeners[name]) {
|
||||
this.eventListeners[name] = [];
|
||||
}
|
||||
|
||||
this.eventListeners[name].push(callback);
|
||||
};
|
||||
|
||||
AudioRecorder.prototype.removeEventListener = function(name, callback) {
|
||||
if (!name || !callback || !this.eventListeners[name]) {
|
||||
return;
|
||||
}
|
||||
|
||||
var newArray = [];
|
||||
this.eventListeners[name].forEach(function(listener) {
|
||||
if (callback != listener) {
|
||||
newArray.push(listener);
|
||||
}
|
||||
});
|
||||
|
||||
this.eventListeners[name] = newArray;
|
||||
};
|
||||
|
||||
AudioRecorder.prototype.start = function() {
|
||||
return new Promise(function(resolve, reject) {
|
||||
this.onstart = function() {
|
||||
this.onstart = null;
|
||||
this.onerror = null;
|
||||
resolve(1);
|
||||
}.bind(this);
|
||||
|
||||
this.onerror = function() {
|
||||
this.onstart = null;
|
||||
this.onerror = null;
|
||||
resolve(0);
|
||||
}.bind(this);
|
||||
|
||||
this.sender({ type: "start" });
|
||||
}.bind(this));
|
||||
};
|
||||
|
||||
AudioRecorder.prototype.stop = function() {
|
||||
return new Promise(function(resolve, reject) {
|
||||
// To make sure the Player in Java can fetch data immediately, we
|
||||
// need to return after data is back.
|
||||
this.ondata = function ondata(message) {
|
||||
_cleanEventListeners();
|
||||
|
||||
// The audio data we received are encoded with a proper format, it doesn't
|
||||
// make sense to concatenate them like the socket, so let just override
|
||||
// the buffered data here.
|
||||
this.data = new Uint8Array(message.data);
|
||||
resolve(1);
|
||||
}.bind(this);
|
||||
|
||||
var _onerror = function() {
|
||||
_cleanEventListeners();
|
||||
resolve(0);
|
||||
}.bind(this);
|
||||
|
||||
var _cleanEventListeners = function() {
|
||||
this.ondata = null;
|
||||
this.removeEventListener("error", _onerror);
|
||||
}.bind(this);
|
||||
|
||||
this.addEventListener("error", _onerror);
|
||||
this.sender({ type: "stop" });
|
||||
}.bind(this));
|
||||
};
|
||||
|
||||
AudioRecorder.prototype.pause = function() {
|
||||
return new Promise(function(resolve, reject) {
|
||||
// In Java, |stopRecord| might be called before |commit|, which triggers
|
||||
// the calling sequence:
|
||||
// nPause -> nGetRecordedSize -> nGetRecordedData -> nClose
|
||||
//
|
||||
// to make sure the Player in Java can fetch data in such a case, we
|
||||
// need to request data immediately.
|
||||
//
|
||||
this.ondata = function ondata(message) {
|
||||
this.ondata = null;
|
||||
|
||||
// The audio data we received are encoded with a proper format, it doesn't
|
||||
// make sense to concatenate them like the socket, so let just override
|
||||
// the buffered data here.
|
||||
this.data = new Uint8Array(message.data);
|
||||
resolve(1);
|
||||
}.bind(this);
|
||||
|
||||
// Have to request data first before pausing.
|
||||
this.requestData();
|
||||
this.sender({ type: "pause" });
|
||||
}.bind(this));
|
||||
};
|
||||
|
||||
AudioRecorder.prototype.requestData = function() {
|
||||
this.sender({ type: "requestData" });
|
||||
};
|
||||
|
||||
AudioRecorder.prototype.close = function() {
|
||||
if (this._closed) {
|
||||
return new Promise(function(resolve) { resolve(1); });
|
||||
}
|
||||
|
||||
// Make sure recording is stopped on the other side.
|
||||
return this.stop().then(function() {
|
||||
DumbPipe.close(this.sender);
|
||||
this._closed = true;
|
||||
}.bind(this));
|
||||
};
|
||||
|
||||
Native.create("com/sun/mmedia/PlayerImpl.nInit.(IILjava/lang/String;)I", function(appId, pId, jURI) {
|
||||
var url = util.fromJavaString(jURI);
|
||||
var id = pId + (appId << 32);
|
||||
|
@ -593,7 +770,6 @@ Native.create("com/sun/mmedia/PlayerImpl.nRealize.(ILjava/lang/String;)Z", funct
|
|||
return player.realize(mime);
|
||||
}, true);
|
||||
|
||||
|
||||
Native.create("com/sun/mmedia/MediaDownload.nGetJavaBufferSize.(I)I", function(handle) {
|
||||
var player = Media.PlayerCache[handle];
|
||||
return player.getBufferSize();
|
||||
|
@ -746,6 +922,60 @@ Native.create("com/sun/mmedia/DirectPlayer.nSetVisible.(IZ)Z", function(handle,
|
|||
return true;
|
||||
});
|
||||
|
||||
Native.create("com/sun/mmedia/DirectPlayer.nIsRecordControlSupported.(I)Z", function(handle) {
|
||||
return !!(Media.PlayerCache[handle] && Media.PlayerCache[handle].audioRecorder);
|
||||
});
|
||||
|
||||
Native.create("com/sun/mmedia/DirectRecord.nSetLocator.(ILjava/lang/String;)I", function(handle, locator) {
|
||||
console.warn("com/sun/mmedia/DirectRecord.nSetLocator.(I)I not implemented.");
|
||||
return -1;
|
||||
});
|
||||
|
||||
Native.create("com/sun/mmedia/DirectRecord.nGetRecordedSize.(I)I", function(handle) {
|
||||
return Media.PlayerCache[handle].getRecordedSize();
|
||||
});
|
||||
|
||||
Native.create("com/sun/mmedia/DirectRecord.nGetRecordedData.(III[B)I", function(handle, offset, size, buffer) {
|
||||
Media.PlayerCache[handle].getRecordedData(offset, size, buffer);
|
||||
return 1;
|
||||
});
|
||||
|
||||
Native.create("com/sun/mmedia/DirectRecord.nCommit.(I)I", function(handle) {
|
||||
// In DirectRecord.java, before nCommit, nPause or nStop is called,
|
||||
// which means all the recorded data has been fetched, so do nothing here.
|
||||
return 1;
|
||||
});
|
||||
|
||||
Native.create("com/sun/mmedia/DirectRecord.nPause.(I)I", function(handle) {
|
||||
return Media.PlayerCache[handle].audioRecorder.pause();
|
||||
}, true);
|
||||
|
||||
Native.create("com/sun/mmedia/DirectRecord.nStop.(I)I", function(handle) {
|
||||
return Media.PlayerCache[handle].audioRecorder.stop();
|
||||
}, true);
|
||||
|
||||
Native.create("com/sun/mmedia/DirectRecord.nClose.(I)I", function(handle) {
|
||||
var player = Media.PlayerCache[handle];
|
||||
|
||||
if (!player || !player.audioRecorder) {
|
||||
// We need to check if |audioRecorder| is still available, because |nClose|
|
||||
// might be called twice in DirectRecord.java, and only IOException is
|
||||
// handled in DirectRecord.java, let use IOException instead of IllegalStateException.
|
||||
throw new JavaException("java/io/IOException");
|
||||
}
|
||||
|
||||
return player.audioRecorder.close().then(function(result) {
|
||||
delete player.audioRecorder;
|
||||
return result;
|
||||
});
|
||||
}, true);
|
||||
|
||||
Native.create("com/sun/mmedia/DirectRecord.nStart.(I)I", function(handle) {
|
||||
// In DirectRecord.java, nStart plays two roles: real start and resume.
|
||||
// Let's handle this on the other side of the DumbPipe.
|
||||
return Media.PlayerCache[handle].audioRecorder.start();
|
||||
}, true);
|
||||
|
||||
/**
|
||||
* @return the volume level between 0 and 100 if succeeded. Otherwise -1.
|
||||
*/
|
||||
|
|
|
@ -80,9 +80,9 @@ Native.create("com/sun/midp/io/j2me/socket/Protocol.open0.([BI)V", function(ipBy
|
|||
|
||||
this.socket.ondata = (function(message) {
|
||||
// console.log("this.socket.ondata: " + JSON.stringify(message));
|
||||
var newArray = new Uint8Array(this.data.byteLength + message.data.length);
|
||||
var newArray = new Uint8Array(this.data.byteLength + message.data.byteLength);
|
||||
newArray.set(this.data);
|
||||
newArray.set(message.data, this.data.byteLength);
|
||||
newArray.set(new Uint8Array(message.data), this.data.byteLength);
|
||||
this.data = newArray;
|
||||
|
||||
if (this.waitingData) {
|
||||
|
|
16
native.js
16
native.js
|
@ -76,6 +76,9 @@ Native.create("java/lang/System.getProperty0.(Ljava/lang/String;)Ljava/lang/Stri
|
|||
case "microedition.amms.version":
|
||||
value = "1.1";
|
||||
break;
|
||||
case "microedition.media.version":
|
||||
value = '1.2';
|
||||
break;
|
||||
case "mmapi-configuration":
|
||||
value = null;
|
||||
break;
|
||||
|
@ -143,6 +146,10 @@ Native.create("java/lang/System.getProperty0.(Ljava/lang/String;)Ljava/lang/Stri
|
|||
console.warn("Property 'com.nokia.multisim.imsi.sim2' is a stub");
|
||||
value = null;
|
||||
break;
|
||||
case "com.nokia.mid.batterylevel":
|
||||
// http://developer.nokia.com/community/wiki/Checking_battery_level_in_Java_ME
|
||||
value = Math.floor(navigator.battery.level * 100).toString();
|
||||
break;
|
||||
case "com.nokia.mid.imsi":
|
||||
console.warn("Property 'com.nokia.mid.imsi' is a stub");
|
||||
value = "000000000000000";
|
||||
|
@ -170,6 +177,15 @@ Native.create("java/lang/System.getProperty0.(Ljava/lang/String;)Ljava/lang/Stri
|
|||
case "classpathext":
|
||||
value = null;
|
||||
break;
|
||||
case "supports.audio.capture":
|
||||
value = "true";
|
||||
break;
|
||||
case "supports.recording":
|
||||
value = "true";
|
||||
break;
|
||||
case "audio.encodings":
|
||||
value = "audio/ogg";
|
||||
break;
|
||||
default:
|
||||
console.warn("UNKNOWN PROPERTY (java/lang/System): " + util.fromJavaString(key));
|
||||
value = null;
|
||||
|
|
|
@ -0,0 +1,166 @@
|
|||
package org.mozilla;
|
||||
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.IOException;
|
||||
|
||||
import javax.microedition.lcdui.Command;
|
||||
import javax.microedition.lcdui.CommandListener;
|
||||
import javax.microedition.lcdui.Display;
|
||||
import javax.microedition.lcdui.Displayable;
|
||||
import javax.microedition.lcdui.Form;
|
||||
import javax.microedition.lcdui.Item;
|
||||
import javax.microedition.lcdui.ItemCommandListener;
|
||||
import javax.microedition.lcdui.StringItem;
|
||||
import javax.microedition.media.Manager;
|
||||
import javax.microedition.media.MediaException;
|
||||
import javax.microedition.media.Player;
|
||||
import javax.microedition.media.control.RecordControl;
|
||||
import javax.microedition.midlet.MIDlet;
|
||||
import javax.microedition.midlet.MIDletStateChangeException;
|
||||
import javax.microedition.media.PlayerListener;
|
||||
|
||||
public class AudioRecorder extends MIDlet implements CommandListener,
|
||||
ItemCommandListener {
|
||||
private Form form;
|
||||
|
||||
private Display display;
|
||||
|
||||
private StringItem recordButton;
|
||||
|
||||
private boolean recording = false;
|
||||
|
||||
private Player player = null;
|
||||
|
||||
private RecordControl recordControl = null;
|
||||
|
||||
private ByteArrayOutputStream outputStream = null;
|
||||
|
||||
private void logMessage(String msg) {
|
||||
this.form.insert(2, new StringItem(null, msg));
|
||||
}
|
||||
|
||||
private class StopThread extends Thread {
|
||||
public void run() {
|
||||
try {
|
||||
recordControl.stopRecord();
|
||||
recordControl.commit();
|
||||
player.stop();
|
||||
player.close();
|
||||
|
||||
logMessage("Start to playback the recorded audio.");
|
||||
String audioEncodings = System.getProperty("audio.encodings");
|
||||
Player playback = Manager.createPlayer(
|
||||
new ByteArrayInputStream(outputStream.toByteArray()),
|
||||
(audioEncodings != null && audioEncodings.trim() != "") ? audioEncodings : "audio/amr");
|
||||
playback.realize();
|
||||
playback.prefetch();
|
||||
playback.start();
|
||||
} catch (IOException e) {
|
||||
logMessage("Error occurs when stop recording: " + e);
|
||||
} catch (MediaException e) {
|
||||
logMessage("Error occurs when stop recording: " + e);
|
||||
}
|
||||
|
||||
logMessage("Recording stopped.");
|
||||
}
|
||||
}
|
||||
|
||||
private class RecordingThread extends Thread {
|
||||
public void run() {
|
||||
logMessage("Start a new thread to record audio.");
|
||||
try {
|
||||
player = Manager.createPlayer("capture://audio");
|
||||
player.realize();
|
||||
|
||||
player.addPlayerListener(new PlayerListener() {
|
||||
public void playerUpdate(Player player, String event, Object eventData) {
|
||||
if (PlayerListener.RECORD_ERROR.equals(event)) {
|
||||
logMessage("Error occurs when start recording: " + eventData);
|
||||
recording = false;
|
||||
updateRecordingMessage();
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
recordControl = (RecordControl) player
|
||||
.getControl("RecordControl");
|
||||
outputStream = new ByteArrayOutputStream();
|
||||
recordControl.setRecordStream(outputStream);
|
||||
recordControl.startRecord();
|
||||
player.start();
|
||||
} catch (IOException e) {
|
||||
recording = false;
|
||||
updateRecordingMessage();
|
||||
logMessage("Error occurs when capturing audio: " + e);
|
||||
} catch (MediaException e) {
|
||||
recording = false;
|
||||
updateRecordingMessage();
|
||||
logMessage("Error occurs when capturing audio: " + e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public AudioRecorder() {
|
||||
}
|
||||
|
||||
private void stopRecording() {
|
||||
this.recording = false;
|
||||
this.updateRecordingMessage();
|
||||
new StopThread().start();
|
||||
}
|
||||
|
||||
private void startRecording() {
|
||||
this.recording = true;
|
||||
this.updateRecordingMessage();
|
||||
new RecordingThread().start();
|
||||
}
|
||||
|
||||
private void updateRecordingMessage() {
|
||||
this.recordButton.setText(this.recording ? "Stop" : "Start");
|
||||
}
|
||||
|
||||
private void toggleRecorderStatus() {
|
||||
// Check if the device support audio capturing.
|
||||
if (!"true".equals(System.getProperty("supports.audio.capture"))) {
|
||||
this.logMessage("This device doesn't support audio capture!");
|
||||
return;
|
||||
}
|
||||
|
||||
if (this.recording) {
|
||||
this.stopRecording();
|
||||
} else {
|
||||
this.startRecording();
|
||||
}
|
||||
}
|
||||
|
||||
public void commandAction(Command command, Item item) {
|
||||
if (item == this.recordButton) {
|
||||
this.toggleRecorderStatus();
|
||||
}
|
||||
}
|
||||
|
||||
public void commandAction(Command c, Displayable d) {
|
||||
}
|
||||
|
||||
protected void destroyApp(boolean unconditional)
|
||||
throws MIDletStateChangeException {
|
||||
}
|
||||
|
||||
protected void pauseApp() {
|
||||
}
|
||||
|
||||
protected void startApp() throws MIDletStateChangeException {
|
||||
this.recordButton = new StringItem(null, "Start", Item.BUTTON);
|
||||
Command toggleRecordingCMD = new Command("Click", Command.ITEM, 1);
|
||||
this.recordButton.addCommand(toggleRecordingCMD);
|
||||
this.recordButton.setDefaultCommand(toggleRecordingCMD);
|
||||
this.recordButton.setItemCommandListener(this);
|
||||
|
||||
this.form = new Form(null, new Item[] {
|
||||
new StringItem(null, "Audio Recorder"), this.recordButton });
|
||||
|
||||
this.display = Display.getDisplay(this);
|
||||
this.display.setCurrent(this.form);
|
||||
}
|
||||
}
|
Загрузка…
Ссылка в новой задаче