Skip to content
This repository has been archived by the owner on Aug 11, 2020. It is now read-only.

Commit

Permalink
Refactor windows implementation in promise manner
Browse files Browse the repository at this point in the history
  • Loading branch information
vladimir-kotikov committed Dec 23, 2015
1 parent 02c67a2 commit 0d01bde
Showing 1 changed file with 177 additions and 127 deletions.
304 changes: 177 additions & 127 deletions src/windows/BarcodeScannerProxy.js
Original file line number Diff line number Diff line change
Expand Up @@ -84,18 +84,124 @@ function videoPreviewRotationLookup(displayOrientation, isMirrored) {
degreesToRotate = 270;
break;
case Windows.Graphics.Display.DisplayOrientations.landscape:
/* falls through */
default:
degreesToRotate = 0;
break;
}

if (isMirrored) {
degreesToRotate = (360 - degreesToRotate) % 360
degreesToRotate = (360 - degreesToRotate) % 360;
}

return degreesToRotate;
}

/**
* The pure JS implementation of barcode reader from WinRTBarcodeReader.winmd.
* Works only on Windows 10 devices and more efficient than original one.
*
* @class {BarcodeReader}
*/
function BarcodeReader () {
this._promise = null;
this._cancelled = false;
}

/**
* Returns an instance of Barcode reader, depending on capabilities of Media
* Capture API
*
* @static
* @constructs {BarcodeReader}
*
* @param {MediaCapture} mediaCaptureInstance Instance of
* Windows.Media.Capture.MediaCapture class
*
* @return {BarcodeReader} BarcodeReader instance that could be used for
* scanning
*/
BarcodeReader.get = function (mediaCaptureInstance) {
if (mediaCaptureInstance.getPreviewFrameAsync && ZXing.BarcodeReader) {
return new BarcodeReader();
}

// If there is no corresponding API (Win8/8.1/Phone8.1) use old approach with WinMD library
return new WinRTBarcodeReader.Reader();

};

/**
* Initializes instance of reader.
*
* @param {MediaCapture} capture Instance of
* Windows.Media.Capture.MediaCapture class, used for acquiring images/ video
* stream for barcode scanner.
* @param {Number} width Video/image frame width
* @param {Number} height Video/image frame height
*/
BarcodeReader.prototype.init = function (capture, width, height) {
this._capture = capture;
this._width = width;
this._height = height;
this._zxingReader = new ZXing.BarcodeReader();
};

/**
* Starts barcode search routines asyncronously.
*
* @return {Promise<ScanResult>} barcode scan result or null if search
* cancelled.
*/
BarcodeReader.prototype.readCode = function () {

/**
* Grabs a frame from preview stream uning Win10-only API and tries to
* get a barcode using zxing reader provided. If there is no barcode
* found, returns null.
*/
function scanBarcodeAsync(mediaCapture, zxingReader, frameWidth, frameHeight) {
// Shortcuts for namespaces
var Imaging = Windows.Graphics.Imaging;
var Streams = Windows.Storage.Streams;

var frame = new Windows.Media.VideoFrame(Imaging.BitmapPixelFormat.bgra8, frameWidth, frameHeight);
return mediaCapture.getPreviewFrameAsync(frame)
.then(function (capturedFrame) {

// Copy captured frame to buffer for further deserialization
var bitmap = capturedFrame.softwareBitmap;
var rawBuffer = new Streams.Buffer(bitmap.pixelWidth * bitmap.pixelHeight * 4);
capturedFrame.softwareBitmap.copyToBuffer(rawBuffer);
capturedFrame.close();

// Get raw pixel data from buffer
var data = new Uint8Array(rawBuffer.length);
var dataReader = Streams.DataReader.fromBuffer(rawBuffer);
dataReader.readBytes(data);
dataReader.close();

return zxingReader.decode(data, frameWidth, frameHeight, ZXing.BitmapFormat.bgra32);
});
}

var self = this;
return scanBarcodeAsync(this._capture, this._zxingReader, this._width, this._height)
.then(function (result) {
if (self._cancelled)
return null;

return result || (self._promise = self.readCode());
});
};

/**
* Stops barcode search
*/
BarcodeReader.prototype.stop = function () {
this._cancelled = true;
};

module.exports = {

/**
Expand Down Expand Up @@ -128,9 +234,9 @@ module.exports = {
var rotDegree = videoPreviewRotationLookup(currentOrientation, previewMirroring);

// rotate the preview video
var videoEncodingProperties = capture.videoDeviceController.getMediaStreamProperties(Windows.Media.Capture.MediaStreamType.VideoPreview);
var videoEncodingProperties = capture.videoDeviceController.getMediaStreamProperties(Windows.Media.Capture.MediaStreamType.videoPreview);
videoEncodingProperties.properties.insert(ROTATION_KEY, rotDegree);
return capture.setEncodingPropertiesAsync(Windows.Media.Capture.MediaStreamType.VideoPreview, videoEncodingProperties, null);
return capture.videoDeviceController.setMediaStreamPropertiesAsync(Windows.Media.Capture.MediaStreamType.videoPreview, videoEncodingProperties);
}

/**
Expand Down Expand Up @@ -182,7 +288,7 @@ module.exports = {
var result = WinJS.Promise.wrap();

if (!capturePreview || capturePreview.paused) {
// If the preview is not yet palying, there is no sense in running focus
// If the preview is not yet playing, there is no sense in running focus
return result;
}

Expand Down Expand Up @@ -238,133 +344,57 @@ module.exports = {
* Starts stream transmission to preview frame and then run barcode search
*/
function startPreview() {
findCamera()
return findCamera()
.then(function (id) {
var captureSettings = new Windows.Media.Capture.MediaCaptureInitializationSettings();
captureSettings.streamingCaptureMode = Windows.Media.Capture.StreamingCaptureMode.video;
captureSettings.photoCaptureSource = Windows.Media.Capture.PhotoCaptureSource.videoPreview;
captureSettings.videoDeviceId = id;

capture = new Windows.Media.Capture.MediaCapture();
capture.initializeAsync(captureSettings).done(function () {

var controller = capture.videoDeviceController;
var deviceProps = controller.getAvailableMediaStreamProperties(Windows.Media.Capture.MediaStreamType.videoRecord);

deviceProps = Array.prototype.slice.call(deviceProps);
deviceProps = deviceProps.filter(function (prop) {
// filter out streams with "unknown" subtype - causes errors on some devices
return prop.subtype !== "Unknown";
}).sort(function (propA, propB) {
// sort properties by resolution
return propB.width - propA.width;
});

var maxResProps = deviceProps[0];

controller.setMediaStreamPropertiesAsync(Windows.Media.Capture.MediaStreamType.videoRecord, maxResProps).done(function () {

capturePreview.src = URL.createObjectURL(capture);
capturePreview.play();

// Insert preview frame and controls into page
document.body.appendChild(capturePreviewFrame);

setupFocus(controller.focusControl)
.then(function () {
Windows.Graphics.Display.DisplayInformation.getForCurrentView().addEventListener("orientationchanged", updatePreviewForRotation, false);
return updatePreviewForRotation();
})
.then(function () {
return startBarcodeSearch(maxResProps.width, maxResProps.height);
})
.done(function (result) {
destroyPreview();
success({
text: result && result.text,
format: result && BARCODE_FORMAT[result.barcodeFormat],
cancelled: !result
});
}, function (error) {
destroyPreview();
fail(error);
});
});
});
});
}

/**
* Starts barcode search process, implemented in WinRTBarcodeReader.winmd library
* Calls success callback, when barcode found.
*/
function startBarcodeSearch(width, height) {

if (!capture.getPreviewFrameAsync || !ZXing.BarcodeReader) {
// If there is no corresponding API (Win8/8.1/Phone8.1) use old approach with WinMD library
reader = new WinRTBarcodeReader.Reader();
reader.init(capture, width, height);
return reader.readCode();
}

reader = {
_promise: null,
_cancelled: false,
_zxingReader: new ZXing.BarcodeReader(),

readCode: function () {

var self = this;
return scanBarcodeAsync(capture, this._zxingReader, width, height)
.then(function (result) {
if (self._cancelled)
return null;

return result || (self._promise = self.readCode());
});
},
return capture.initializeAsync(captureSettings);
})
.then(function () {

stop: function () {
this._cancelled = true;
}
};
var controller = capture.videoDeviceController;
var deviceProps = controller.getAvailableMediaStreamProperties(Windows.Media.Capture.MediaStreamType.videoRecord);

// Add a small timeout before capturing first frame otherwise
// we would get an 'Invalid state' error from 'getPreviewFrameAsync'
return WinJS.Promise.timeout(200)
.then(function () {
return reader.readCode();
});
}
deviceProps = Array.prototype.slice.call(deviceProps);
deviceProps = deviceProps.filter(function (prop) {
// filter out streams with "unknown" subtype - causes errors on some devices
return prop.subtype !== "Unknown";
}).sort(function (propA, propB) {
// sort properties by resolution
return propB.width - propA.width;
});

/**
* Grabs a frame from preview stream uning Win10-only API and tries to
* get a barcode using zxing reader provided. If there is no barcode
* found, returns null.
*/
function scanBarcodeAsync(mediaCapture, zxingReader, frameWidth, frameHeight) {
// Shortcuts for namespaces
var Imaging = Windows.Graphics.Imaging;
var Streams = Windows.Storage.Streams;

var frame = new Windows.Media.VideoFrame(Imaging.BitmapPixelFormat.bgra8, frameWidth, frameHeight);
return mediaCapture.getPreviewFrameAsync(frame)
.then(function (capturedFrame) {

// Copy captured frame to buffer for further deserialization
var bitmap = capturedFrame.softwareBitmap;
var rawBuffer = new Streams.Buffer(bitmap.pixelWidth * bitmap.pixelHeight * 4);
capturedFrame.softwareBitmap.copyToBuffer(rawBuffer);
capturedFrame.close();

// Get raw pixel data from buffer
var data = new Uint8Array(rawBuffer.length);
var dataReader = Streams.DataReader.fromBuffer(rawBuffer);
dataReader.readBytes(data);
dataReader.close();

var result = zxingReader.decode(data, frameWidth, frameHeight, ZXing.BitmapFormat.bgra32);
return WinJS.Promise.wrap(result);
var maxResProps = deviceProps[0];
return controller.setMediaStreamPropertiesAsync(Windows.Media.Capture.MediaStreamType.videoRecord, maxResProps)
.then(function () {
return {
capture: capture,
width: maxResProps.width,
height: maxResProps.height
};
});
})
.then(function (captureSettings) {

capturePreview.msZoom = true;
capturePreview.src = URL.createObjectURL(capture);
capturePreview.play();

// Insert preview frame and controls into page
document.body.appendChild(capturePreviewFrame);

return setupFocus(captureSettings.capture.videoDeviceController.focusControl)
.then(function () {
Windows.Graphics.Display.DisplayInformation.getForCurrentView().addEventListener("orientationchanged", updatePreviewForRotation, false);
return updatePreviewForRotation();
})
.then(function () {
return captureSettings;
});
});
}

Expand Down Expand Up @@ -398,12 +428,32 @@ module.exports = {
reader && reader.stop();
}

try {
createPreview();
startPreview();
} catch (ex) {
fail(ex);
}
WinJS.Promise.wrap(createPreview())
.then(function () {
return startPreview();
})
.then(function (captureSettings) {
reader = BarcodeReader.get(captureSettings.capture);
reader.init(captureSettings.capture, captureSettings.width, captureSettings.height);

// Add a small timeout before capturing first frame otherwise
// we would get an 'Invalid state' error from 'getPreviewFrameAsync'
return WinJS.Promise.timeout(200)
.then(function () {
return reader.readCode();
});
})
.done(function (result) {
destroyPreview();
success({
text: result && result.text,
format: result && BARCODE_FORMAT[result.barcodeFormat],
cancelled: !result
});
}, function (error) {
destroyPreview();
fail(error);
});
},

/**
Expand Down

1 comment on commit 0d01bde

@SunboX
Copy link
Contributor

@SunboX SunboX commented on 0d01bde Dec 27, 2015

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👍

Please sign in to comment.