/**
 * Copyright (c) Microsoft Corporation.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
import { renderTitleForCall } from '@isomorphic/protocolFormatter';
import { debugLogger } from '@utils/debugLogger';
export class Screencast {
    page;
    _clients = new Set();
    _actions;
    _size;
    _lastFrame;
    constructor(page) {
        this.page = page;
        this.page.instrumentation.addListener(this, page.browserContext);
    }
    async handlePageOrContextClose() {
        const clients = [...this._clients];
        this._clients.clear();
        for (const client of clients) {
            if (client.gracefulClose)
                await client.gracefulClose();
        }
    }
    dispose() {
        for (const client of this._clients)
            client.dispose();
        this._clients.clear();
        this.page.instrumentation.removeListener(this);
    }
    showActions(options) {
        this._actions = options;
    }
    hideActions() {
        this._actions = undefined;
    }
    addClient(client) {
        const isFirst = this._clients.size === 0;
        this._clients.add(client);
        if (isFirst) {
            this._startScreencast(client.size, client.quality);
        }
        else if (this._lastFrame) {
            // Deliver the cached last frame to the new client so it does not have
            // to wait for the next browser repaint. setTimeout(0) ensures the caller
            // of addClient() finishes before the frame is dispatched.
            const frame = this._lastFrame;
            setTimeout(() => {
                if (this._clients.has(client))
                    void client.onFrame(frame);
            }, 0);
        }
        return { size: this._size };
    }
    removeClient(client) {
        if (!this._clients.has(client))
            return;
        this._clients.delete(client);
        if (!this._clients.size)
            this._stopScreencast();
    }
    _startScreencast(size, quality) {
        this._size = size;
        if (!this._size) {
            const viewport = this.page.browserContext._options.viewport || { width: 800, height: 600 };
            const scale = Math.min(1, 800 / Math.max(viewport.width, viewport.height));
            this._size = {
                width: Math.floor(viewport.width * scale),
                height: Math.floor(viewport.height * scale)
            };
        }
        // Make sure both dimensions are odd, this is required for vp8
        this._size = {
            width: this._size.width & ~1,
            height: this._size.height & ~1
        };
        this.page.delegate.startScreencast({
            width: this._size.width,
            height: this._size.height,
            quality: quality ?? 90,
        });
    }
    _stopScreencast() {
        this._lastFrame = undefined;
        this.page.delegate.stopScreencast();
    }
    onScreencastFrame(frame, ack) {
        this._lastFrame = frame;
        const asyncResults = [];
        for (const client of this._clients) {
            const result = client.onFrame(frame);
            if (result)
                asyncResults.push(result);
        }
        if (ack) {
            // Ack when any client resolves (OR logic). This ensures that even if
            // tracing throttles its response, other clients (like video) that resolve
            // immediately keep frames flowing.
            if (!asyncResults.length)
                ack();
            else
                Promise.race(asyncResults).then(ack);
        }
    }
    async onBeforeCall(sdkObject, metadata, parentId) {
        if (!this._actions)
            return;
        metadata.annotate = true;
    }
    async onBeforeInputAction(sdkObject, metadata) {
        if (!this._actions)
            return;
        const page = sdkObject.attribution.page;
        if (!page)
            return;
        const actionTitle = renderTitleForCall(metadata);
        const utility = await page.mainFrame().utilityContext();
        // Run this outside of the progress timer.
        await utility.evaluate(async (options) => {
            const { injected, duration } = options;
            injected.setScreencastAnnotation(options);
            await new Promise(f => injected.utils.builtins.setTimeout(f, duration));
            injected.setScreencastAnnotation(null);
        }, {
            injected: await utility.injectedScript(),
            duration: this._actions?.duration ?? 500,
            point: metadata.point,
            box: metadata.box,
            actionTitle,
            position: this._actions?.position,
            fontSize: this._actions?.fontSize,
        }).catch(e => debugLogger.log('error', e));
    }
}
//# sourceMappingURL=screencast.js.map