const Emitter = require('./emitter');
const Constants = require('./constants');
const Utils = require('./utils');
const WIDTH = 'Width';
const HEIGHT = 'Height';
/**
* @hideconstructor
* @classdesc Outgoing image class. Instances are returned from <code>Session.sendImage</code> method
**/
class OutgoingImage extends Emitter {
constructor(session, instanceOptions = {}) {
super();
this.options = Object.assign({
thumbnailCompress: 0.3,
fullImageCompress: 0.8,
cameraAspectRatio: 4/3,
preview: true
}, session.options, instanceOptions);
this.session = session;
this.currentMessageId = null;
this.img = null;
this.canvas = null;
this.canvasContext = null;
this.initialWidth = 0;
this.initialHeight = 0;
this.fullImageWidth = 0;
this.fullImageHeight = 0;
this.thumbnailWidth = 0;
this.thumbnailHeight = 0;
this.source = '';
this.thumbnailData = null;
this.fullImageData = null;
this.player = null;
// todo: validate availale browser APIs
if (this.options.file) {
this.file = this.options.file;
this.handleFile();
} else {
this.handleCamera();
}
this.setHandlers();
}
blobHandler(type) {
return (blob) => {
//let packet = Utils.buildBinaryPacket(2, 666, 2, thumbnailData);
let reader = new FileReader();
reader.addEventListener('loadend', (e) => {
this.emit(type + '_data', reader.result);
});
reader.readAsArrayBuffer(blob);
}
}
readyToSend() {
return this.thumbnailData && this.fullImageData;
}
setHandlers() {
this.on(Constants.EVENT_THUMBNAIL_PREVIEW_DATA, this.blobHandler('thumbnail'));
this.on(Constants.EVENT_IMAGE_PREVIEW_DATA, this.blobHandler('image'));
this.on(Constants.EVENT_THUMBNAIL_DATA, (data) => {
this.thumbnailData = data;
this.processFullImage();
});
this.on(Constants.EVENT_IMAGE_DATA, (data) => {
this.fullImageData = data;
if (this.options.preview) {
return;
}
this.send();
});
}
/**
* Sends an outgoing image
* call this method if <code>options.preview</code> is set to <code>true</code> (default behaviour).
* Otherwise image is sent automatically when instance is created by <code>session.sendImage</code>
* **/
send() {
if (!this.readyToSend()) {
throw new Error(Constants.ERROR_IMAGE_NOT_READY_TO_BE_SENT);
}
const params = {
seq: this.session.getSeq(),
command: 'send_image',
type: 'jpeg',
thumbnail_content_length: this.thumbnailData.length,
content_length: this.fullImageData.length,
width: this.fullImageWidth,
height: this.fullImageHeight,
source: this.source
};
if (this.options.for) {
params.for = this.options.for;
}
this.session.sendCommand(params, (err, data) => {
if (err) {
throw new Error(err ? err : Constants.ERROR_FAILED_TO_SEND_IMAGE);
}
this.currentMessageId = data.image_id;
this.sendData();
});
}
sendData() {
this.session.sendBinary(Utils.buildBinaryPacket(
Constants.MESSAGE_TYPE_IMAGE,
this.currentMessageId,
Constants.IMAGE_TYPE_THUMBNAIL,
this.thumbnailData
));
this.session.sendBinary(Utils.buildBinaryPacket(
Constants.MESSAGE_TYPE_IMAGE,
this.currentMessageId,
Constants.IMAGE_TYPE_FULL,
this.fullImageData
));
}
detectSizes() {
let scale = (this.initialHeight > this.initialWidth) ? HEIGHT : WIDTH;
let scaleOver = scale === HEIGHT ? WIDTH : HEIGHT;
this.fullImageWidth = this.initialWidth;
this.fullImageHeight = this.initialHeight;
const prop = this['initial' + scaleOver] / this['initial' + scale];
if (this['initial' + scale] > Constants.MAX_OUTGOING_IMAGE_SCALE_PX) {
this['fullImage' + scale] = Constants.MAX_OUTGOING_IMAGE_SCALE_PX;
this['fullImage' + scaleOver] = parseInt(Constants.MAX_OUTGOING_IMAGE_SCALE_PX * prop, 10);
}
this['thumbnail' + scale] = Constants.OUTGOING_IMAGE_THUMBNAIL_SCALE_PX;
this['thumbnail' + scaleOver] = parseInt(Constants.OUTGOING_IMAGE_THUMBNAIL_SCALE_PX * prop, 10);
}
process() {
this.detectSizes();
this.canvas = document.createElement('canvas');
this.canvasContext = this.canvas.getContext('2d');
this.processThumbnail();
}
processThumbnail() {
this.canvas.width = this.thumbnailWidth;
this.canvas.height = this.thumbnailHeight;
this.canvasContext.drawImage(this.img, 0, 0, this.thumbnailWidth, this.thumbnailHeight);
this.canvas.toBlob((thumbnailBlob) => {
/**
* Outgoing image thumbnail data available for preview
* @event OutgoingImage#thumbnail_preview_data
* @param {Blob} data preview image thumbnail blob
*/
this.emit(Constants.EVENT_THUMBNAIL_PREVIEW_DATA, thumbnailBlob);
}, 'image/jpeg', this.options.thumbnailCompress);
}
processFullImage() {
this.canvas.width = this.fullImageWidth;
this.canvas.height = this.fullImageHeight;
this.canvasContext.drawImage(this.img, 0, 0, this.fullImageWidth, this.fullImageHeight);
this.canvas.toBlob((fullImageBlob) => {
/**
* Outgoing image full size image data available for preview
* @event OutgoingImage#image_preview_data
* @param {Blob} data preview full size image blob
*/
this.emit(Constants.EVENT_IMAGE_PREVIEW_DATA, fullImageBlob);
}, 'image/jpeg', this.options.fullImageCompress);
}
handleCamera() {
const supported = 'mediaDevices' in navigator;
if (!supported) {
throw new Error(Constants.ERROR_NO_CAMERA_AVAILABLE);
}
this.source = 'camera';
navigator.mediaDevices.getUserMedia({video: true}).then((stream) => {
let player = document.createElement('video');
player.autoplay = true;
// safari hack
player.style.cssText = 'position: absolute; left: 0; top: 0; z-index: -10000; opacity: 0.0;';
document.getElementsByTagName('body')[0].append(player);
let canvas = document.createElement('canvas');
let ctx = canvas.getContext('2d');
let cameraAspect = this.options.cameraAspectRatio;
let maxWidth = Constants.MAX_OUTGOING_IMAGE_SCALE_PX;
let maxHeight = maxWidth / cameraAspect;
canvas.width = maxWidth;
canvas.height = maxHeight;
player.width = maxWidth;
player.height = maxHeight;
player.srcObject = stream;
setTimeout(() => {
ctx.drawImage(player, 0, 0, maxWidth, maxHeight);
canvas.toBlob((cameraImageBlob) => {
this.img = new Image();
this.img.onload = () => {
this.initialWidth = maxWidth;
this.initialHeight = maxHeight;
this.process();
player.srcObject.getVideoTracks().forEach(track => track.stop());
};
this.img.src = URL.createObjectURL(cameraImageBlob);
});
}, 2000);
});
}
handleFile() {
this.source = 'library';
this.img = new Image();
this.img.onload = () => {
this.initialWidth = this.img.width;
this.initialHeight = this.img.height;
if (this.initialWidth === 0 || this.initialWidth === 0) {
throw new Error(Constants.ERROR_INVALID_IMAGE_WIDTH_OR_HEIGHT);
}
this.process();
};
this.img.src = URL.createObjectURL(this.file);
}
}
module.exports = OutgoingImage;