Skip to content

Commit

Permalink
V2.3.2 (#616)
Browse files Browse the repository at this point in the history
* Refactor camera component to ./src/camera

 - Refactor camera component to ./src/camera from ./src/html5-qrcode.ts

* Misc fixes (codacy)

* Codacy fixes

* (refactor) Abstract camera selection UI to a separate class

* Add support for zoom slider

And misc changes.

* misc fixes.

* Misc changes per PR comments.

* Update change log + codacy fix.

* Update html5-qrcode.min.js

* Add unit test for camera-selection-ui and camera-zoom-ui

* Refactor TorchButton and remove html5qrcode deps

To make it feasible to test the class.

* Create torch-button.test.ts

* Update torch-button.test.ts
  • Loading branch information
mebjas authored Nov 21, 2022
1 parent 6710464 commit 47ebd5a
Show file tree
Hide file tree
Showing 20 changed files with 1,056 additions and 233 deletions.
23 changes: 23 additions & 0 deletions changelog.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,29 @@
#### Features or bug fixes.
- Hide margin of parent container when camera selection UI is hidden (if only 1 camera is found.) - [Issue#599](https://github.com/mebjas/html5-qrcode/issues/599), [PR#607](https://github.com/mebjas/html5-qrcode/pull/607) by [adamwolf@](https://github.com/adamwolf).

**Support for zoom slider in `Html5QrcodeScanner`.**
Added basic support for zoom feature under configuration flag (not enabled by default). This was raised in issue [issue#330](https://github.com/mebjas/html5-qrcode/issues/330).This should help address some focus issues raised so far.

Not supported on Safari or any IOS browser though!

How to use

```js
let html5QrcodeScanner = new Html5QrcodeScanner(
"reader",
{
fps: 10,
qrbox: qrboxFunction,
useBarCodeDetectorIfSupported: true,
rememberLastUsedCamera: true,
aspectRatio: 4/3,
showTorchButtonIfSupported: true,
showZoomSliderIfSupported: true,
defaultZoomValueIfSupported: 2
// ^ this means by default camera will load at 2x zoom.
});
```

#### Tech debts
- Refactored the camera components out of `src/html5-qrcode.ts`

Expand Down
2 changes: 1 addition & 1 deletion minified/html5-qrcode.min.js

Large diffs are not rendered by default.

138 changes: 129 additions & 9 deletions src/camera/core-impl.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,11 +7,127 @@

import {
Camera,
CameraCapabilities,
CameraCapability,
RangeCameraCapability,
CameraRenderingOptions,
RenderedCamera,
RenderingCallbacks
RenderingCallbacks,
BooleanCameraCapability
} from "./core";

/** Interface for a range value. */
interface RangeValue {
min: number;
max: number;
step: number;
}

/** Abstract camera capability class. */
abstract class AbstractCameraCapability<T> implements CameraCapability<T> {
protected readonly name: string;
protected readonly track: MediaStreamTrack;

constructor(name: string, track: MediaStreamTrack) {
this.name = name;
this.track = track;
}

public isSupported(): boolean {
return this.name in this.track.getCapabilities();
}

public apply(value: T): Promise<void> {
let constraint: any = {};
constraint[this.name] = value;
let constraints = { advanced: [ constraint ] };
return this.track.applyConstraints(constraints);
}

public value(): T | null {
let settings: any = this.track.getSettings();
if (this.name in settings) {
let settingValue = settings[this.name];
return settingValue;
}

return null;
}
}

abstract class AbstractRangeCameraCapability extends AbstractCameraCapability<number> {
constructor(name: string, track: MediaStreamTrack) {
super(name, track);
}

public min(): number {
return this.getCapabilities().min;
}

public max(): number {
return this.getCapabilities().max;
}

public step(): number {
return this.getCapabilities().step;
}

public apply(value: number): Promise<void> {
let constraint: any = {};
constraint[this.name] = value;
let constraints = {advanced: [ constraint ]};
return this.track.applyConstraints(constraints);
}

private getCapabilities(): RangeValue {
this.failIfNotSupported();
let capabilities: any = this.track.getCapabilities();
let capability: any = capabilities[this.name];
return {
min: capability.min,
max: capability.max,
step: capability.step,
};
}

private failIfNotSupported() {
if (!this.isSupported()) {
throw new Error(`${this.name} capability not supported`);
}
}
}

/** Zoom feature. */
class ZoomFeatureImpl extends AbstractRangeCameraCapability {
constructor(track: MediaStreamTrack) {
super("zoom", track);
}
}

/** Torch feature. */
class TorchFeatureImpl extends AbstractCameraCapability<boolean> {
constructor(track: MediaStreamTrack) {
super("torch", track);
}
}

/** Implementation of {@link CameraCapabilities}. */
class CameraCapabilitiesImpl implements CameraCapabilities {
private readonly track: MediaStreamTrack;

constructor(track: MediaStreamTrack) {
this.track = track;
}

zoomFeature(): RangeCameraCapability {
return new ZoomFeatureImpl(this.track);
}

torchFeature(): BooleanCameraCapability {
return new TorchFeatureImpl(this.track);
}
}

/** Implementation of {@link RenderedCamera}. */
class RenderedCameraImpl implements RenderedCamera {

Expand Down Expand Up @@ -55,18 +171,18 @@ class RenderedCameraImpl implements RenderedCamera {
throw "RenderedCameraImpl video surface onerror() called";
};

this.surface.addEventListener("playing", () => this.onVideoStart());
let onVideoStart = () => {
const videoWidth = this.surface.clientWidth;
const videoHeight = this.surface.clientHeight;
this.callbacks.onRenderSurfaceReady(videoWidth, videoHeight);
this.surface.removeEventListener("playing", onVideoStart);
};

this.surface.addEventListener("playing", onVideoStart);
this.surface.srcObject = this.mediaStream;
this.surface.play();
}

private onVideoStart() {
const videoWidth = this.surface.clientWidth;
const videoHeight = this.surface.clientHeight;
this.callbacks.onRenderSurfaceReady(videoWidth, videoHeight);
this.surface.removeEventListener("playing", this.onVideoStart);
}

static async create(
parentElement: HTMLElement,
mediaStream: MediaStream,
Expand Down Expand Up @@ -177,6 +293,10 @@ class RenderedCameraImpl implements RenderedCamera {

});
}

getCapabilities(): CameraCapabilities {
return new CameraCapabilitiesImpl(this.getFirstTrackOrFail());
}
//#endregion
}

Expand Down
45 changes: 45 additions & 0 deletions src/camera/core.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,46 @@ export interface CameraDevice {
label: string;
}

//#region Features
/** Generic capability of camera. */
export interface CameraCapability<T> {
/** Returns {@code true} if the capability is supported by the camera. */
isSupported(): boolean;

/** Apply the {@code value} to camera for this capability. */
apply(value: T): Promise<void>;

/** Returns current value of this capability. */
value(): T | null;
}

/** Capability of the camera that has range. */
export interface RangeCameraCapability extends CameraCapability<number> {
/** Min value allowed for this capability. */
min(): number;

/** Max value allowed for this capability. */
max(): number;

/** Steps allowed for this capability. */
step(): number;
}

/** Capability of camera that is boolean in nature. */
export interface BooleanCameraCapability extends CameraCapability<boolean> {}

/** Class exposing different capabilities of camera. */
export interface CameraCapabilities {

/** Zoom capability of the camera. */
zoomFeature(): RangeCameraCapability;

/** Torch capability of the camera. */
torchFeature(): BooleanCameraCapability;
}

//#endregion

/** Type for callback called when camera surface is ready. */
export type OnRenderSurfaceReady
= (viewfinderWidth: number, viewfinderHeight: number) => void;
Expand Down Expand Up @@ -102,6 +142,11 @@ export interface RenderedCamera {
* @throws error if {@link RenderedCamera} instance is already closed.
*/
applyVideoConstraints(constraints: MediaTrackConstraints): Promise<void>;

/**
* Returns all capabilities of the camera.
*/
getCapabilities(): CameraCapabilities;
}

/** Options for rendering camera feed. */
Expand Down
12 changes: 12 additions & 0 deletions src/core.ts
Original file line number Diff line number Diff line change
Expand Up @@ -320,4 +320,16 @@ export class BaseLoggger implements Logger {
export function isNullOrUndefined(obj?: any) {
return (typeof obj === "undefined") || obj === null;
}

/** Clips the {@code value} between {@code minValue} and {@code maxValue}. */
export function clip(value: number, minValue: number, maxValue: number) {
if (value > maxValue) {
return maxValue;
}
if (value < minValue) {
return minValue;
}

return value;
}
//#endregion
Loading

0 comments on commit 47ebd5a

Please sign in to comment.