Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

V2.3.2 #616

Merged
merged 14 commits into from
Nov 21, 2022
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) {
mebjas marked this conversation as resolved.
Show resolved Hide resolved
if (value > maxValue) {
return maxValue;
}
if (value < minValue) {
return minValue;
}

return value;
}
//#endregion
Loading