var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
    function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
    return new (P || (P = Promise))(function (resolve, reject) {
        function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
        function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
        function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
        step((generator = generator.apply(thisArg, _arguments || [])).next());
    });
};
class AbstractCameraCapability {
    constructor(name, track) {
        this.name = name;
        this.track = track;
    }
    isSupported() {
        if (!this.track.getCapabilities) {
            return false;
        }
        return this.name in this.track.getCapabilities();
    }
    apply(value) {
        let constraint = {};
        constraint[this.name] = value;
        let constraints = { advanced: [constraint] };
        return this.track.applyConstraints(constraints);
    }
    value() {
        let settings = this.track.getSettings();
        if (this.name in settings) {
            let settingValue = settings[this.name];
            return settingValue;
        }
        return null;
    }
}
class AbstractRangeCameraCapability extends AbstractCameraCapability {
    constructor(name, track) {
        super(name, track);
    }
    min() {
        return this.getCapabilities().min;
    }
    max() {
        return this.getCapabilities().max;
    }
    step() {
        return this.getCapabilities().step;
    }
    apply(value) {
        let constraint = {};
        constraint[this.name] = value;
        let constraints = { advanced: [constraint] };
        return this.track.applyConstraints(constraints);
    }
    getCapabilities() {
        this.failIfNotSupported();
        let capabilities = this.track.getCapabilities();
        let capability = capabilities[this.name];
        return {
            min: capability.min,
            max: capability.max,
            step: capability.step,
        };
    }
    failIfNotSupported() {
        if (!this.isSupported()) {
            throw new Error(`${this.name} capability not supported`);
        }
    }
}
class ZoomFeatureImpl extends AbstractRangeCameraCapability {
    constructor(track) {
        super("zoom", track);
    }
}
class TorchFeatureImpl extends AbstractCameraCapability {
    constructor(track) {
        super("torch", track);
    }
}
class CameraCapabilitiesImpl {
    constructor(track) {
        this.track = track;
    }
    zoomFeature() {
        return new ZoomFeatureImpl(this.track);
    }
    torchFeature() {
        return new TorchFeatureImpl(this.track);
    }
}
class RenderedCameraImpl {
    constructor(parentElement, mediaStream, callbacks) {
        this.isClosed = false;
        this.parentElement = parentElement;
        this.mediaStream = mediaStream;
        this.callbacks = callbacks;
        this.surface = this.createVideoElement(this.parentElement.clientWidth);
        parentElement.append(this.surface);
    }
    createVideoElement(width) {
        const videoElement = document.createElement("video");
        videoElement.style.width = `${width}px`;
        videoElement.style.display = "block";
        videoElement.muted = true;
        videoElement.setAttribute("muted", "true");
        videoElement.playsInline = true;
        return videoElement;
    }
    setupSurface() {
        this.surface.onabort = () => {
            throw "RenderedCameraImpl video surface onabort() called";
        };
        this.surface.onerror = () => {
            throw "RenderedCameraImpl video surface onerror() called";
        };
        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();
    }
    static create(parentElement, mediaStream, options, callbacks) {
        return __awaiter(this, void 0, void 0, function* () {
            let renderedCamera = new RenderedCameraImpl(parentElement, mediaStream, callbacks);
            if (options.aspectRatio) {
                let aspectRatioConstraint = {
                    aspectRatio: options.aspectRatio
                };
                yield renderedCamera.getFirstTrackOrFail().applyConstraints(aspectRatioConstraint);
            }
            renderedCamera.setupSurface();
            return renderedCamera;
        });
    }
    failIfClosed() {
        if (this.isClosed) {
            throw "The RenderedCamera has already been closed.";
        }
    }
    getFirstTrackOrFail() {
        this.failIfClosed();
        if (this.mediaStream.getVideoTracks().length === 0) {
            throw "No video tracks found";
        }
        return this.mediaStream.getVideoTracks()[0];
    }
    pause() {
        this.failIfClosed();
        this.surface.pause();
    }
    resume(onResumeCallback) {
        this.failIfClosed();
        let $this = this;
        const onVideoResume = () => {
            setTimeout(onResumeCallback, 200);
            $this.surface.removeEventListener("playing", onVideoResume);
        };
        this.surface.addEventListener("playing", onVideoResume);
        this.surface.play();
    }
    isPaused() {
        this.failIfClosed();
        return this.surface.paused;
    }
    getSurface() {
        this.failIfClosed();
        return this.surface;
    }
    getRunningTrackCapabilities() {
        return this.getFirstTrackOrFail().getCapabilities();
    }
    getRunningTrackSettings() {
        return this.getFirstTrackOrFail().getSettings();
    }
    applyVideoConstraints(constraints) {
        return __awaiter(this, void 0, void 0, function* () {
            if ("aspectRatio" in constraints) {
                throw "Changing 'aspectRatio' in run-time is not yet supported.";
            }
            return this.getFirstTrackOrFail().applyConstraints(constraints);
        });
    }
    close() {
        if (this.isClosed) {
            return Promise.resolve();
        }
        let $this = this;
        return new Promise((resolve, _) => {
            let tracks = $this.mediaStream.getVideoTracks();
            const tracksToClose = tracks.length;
            var tracksClosed = 0;
            $this.mediaStream.getVideoTracks().forEach((videoTrack) => {
                $this.mediaStream.removeTrack(videoTrack);
                videoTrack.stop();
                ++tracksClosed;
                if (tracksClosed >= tracksToClose) {
                    $this.isClosed = true;
                    $this.parentElement.removeChild($this.surface);
                    resolve();
                }
            });
        });
    }
    getCapabilities() {
        return new CameraCapabilitiesImpl(this.getFirstTrackOrFail());
    }
}
export class CameraImpl {
    constructor(mediaStream) {
        this.mediaStream = mediaStream;
    }
    render(parentElement, options, callbacks) {
        return __awaiter(this, void 0, void 0, function* () {
            return RenderedCameraImpl.create(parentElement, this.mediaStream, options, callbacks);
        });
    }
    static create(videoConstraints) {
        return __awaiter(this, void 0, void 0, function* () {
            if (!navigator.mediaDevices) {
                throw "navigator.mediaDevices not supported";
            }
            let constraints = {
                audio: false,
                video: videoConstraints
            };
            let mediaStream = yield navigator.mediaDevices.getUserMedia(constraints);
            return new CameraImpl(mediaStream);
        });
    }
}
//# sourceMappingURL=core-impl.js.map