const Emitter = require('./emitter');
const Constants = require('./constants');
const Utils = require('./utils');
/**
* @hideconstructor
* @classdesc Outgoing audio message class. Instances are returned from <code>Session.startVoiceMessage</code> method
**/
class OutgoingMessage extends Emitter {
constructor(session, instanceOptions = {}) {
super();
const library = Utils.getLoadedLibrary();
this.options =
Object.assign({
autoStart: true,
recorderSampleRate: 44100, // fallback for recorder with no getOriginalSampleRate method
encoderFrameSize: 20,
encoderSampleRate: 16000,
encoderApplication: 2048
}, session.options, instanceOptions);
if (this.options.recorder && !Utils.isFunction(this.options.recorder)) {
this.options.recorder = library.Recorder;
}
if (this.options.encoder && !Utils.isFunction(this.options.encoder)) {
this.options.encoder = library.Encoder;
}
this.session = session;
this.currentMessageId = null;
this.currentPacketId = 0;
this.initEncoder();
this.initRecorder();
// start message explicitly if no recorder present.
// if recorder is there it will start from Recorder.onready
if (!this.recorder && this.options.autoStart) {
this.start();
}
}
initEncoder() {
if (!this.options.encoder) {
return;
}
this.options.encoder.prototype.ondata = (data) => {
if (Array.isArray(data)) {
data.forEach((frame) =>
this.processEncodedData(frame)
);
return;
}
this.processEncodedData(data);
};
this.encoder = new this.options.encoder;
}
processEncodedData(data) {
let packet = Utils.buildBinaryPacket(1, this.currentMessageId, ++this.currentPacketId, data);
/**
* Outgoing message packet encoded and ready to be sent to zello server. Session is following this event and sends data automatically
*
* @event OutgoingMessage#data_encoded
* @param {Uint8Array} packet encoded opus packet with headers
*/
this.emit(Constants.EVENT_DATA_ENCODED, packet);
}
initRecorder() {
if (!this.options.recorder) {
return;
}
this.options.recorder.prototype.ondata = (data) => {
/**
* Outgoing message pcm data from recorder is ready to be encoded
*
* @event OutgoingMessage#data
* @param {Float32Array} data pcm data portion
*/
this.emit(Constants.EVENT_DATA, data);
this.encoder.encode(data);
};
this.options.recorder.prototype.onready = () => {
this.sendEncoderInitMessage();
if (this.options.autoStart) {
this.start();
}
};
this.recorder = new this.options.recorder(this.options, this.encoder);
if (Utils.isFunction(this.recorder.init)) {
this.recorder.init();
}
}
sendEncoderInitMessage() {
if (!this.encoder || !Utils.isFunction(this.encoder.postMessage)) {
return;
}
let recorderSampleRate = this.options.recorderSampleRate;
if (Utils.isFunction(this.recorder.getSampleRate)) {
recorderSampleRate = this.recorder.getSampleRate();
}
this.encoder.postMessage(Object.assign({
command: 'init',
originalSampleRate: recorderSampleRate
}, this.options));
}
stopRecording() {
if (this.recorder && this.recorder.stop) {
this.recorder.stop();
}
}
startRecording() {
if (this.recorder && this.recorder.start) {
this.recorder.start();
}
}
/**
* Stops outgoing message
*
* @param {Function} userCallback user callback that is called when server <code>stop_stream</code> command is done
* @returns {Promise} promise that resolves when server <code>stop_stream</code> command is done
* @example
// callback
outgoingMessage.stop(function(err, result) {
if (err) {
console.trace(err);
return;
}
console.warn('Message stopped');
});
// promise
outgoingMessage.then(function(result) {
console.warn('Message stopped');
}).catch(function(err) {
console.trace(err);
});
*/
stop(userCallback) {
this.stopRecording();
return this.session.stopStream({
stream_id: this.currentMessageId
}, userCallback);
}
/**
* Starts an outgoing message
* if <code>options.autoStart</code> is <code>true</code> (default behaviour) then message is started automatically
* when instance is created by <code>session.startVoiceMessage</code>
* **/
start() {
let params = {
'type': 'audio',
'codec': 'opus',
'codec_header': Utils.buildCodecHeader(this.options.encoderSampleRate, 1, this.options.encoderFrameSize),
'packet_duration': this.options.encoderFrameSize
};
if (this.options.for) {
params.for = this.options.for
}
this.session.startStream(params).then((result) => {
this.currentMessageId = result.stream_id;
this.startRecording();
}).catch((err) => {
throw new Error(err);
})
}
}
module.exports = OutgoingMessage;