"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.MediaStreamStatus = void 0;
const ffmpeg = require("fluent-ffmpeg");
const Config_1 = require("../lib/Config");
const Logger_1 = require("../lib/Logger");
const Matrix_1 = require("../remote-services/Matrix");
const Hikvision_1 = require("../remote-services/Hikvision");
const fs_1 = require("fs");
const logger = Logger_1.default.of('store:media-stream');
const CONVERSION_IDLE_TIMEOUT = 60000;
const STREAMS_CACHE_TTL = 60000;
const rtspUriHub = new Map();
const streams = new Map();
let streamsExpiresAt = 0;
setInterval(() => {
    if (Config_1.default.neverStop)
        return;
    streams.forEach((stream) => {
        if (stream.expires) {
            stream.stopConversion();
            streams.delete(stream.id);
        }
    });
}, 1000);
var MediaStreamStatus;
(function (MediaStreamStatus) {
    MediaStreamStatus[MediaStreamStatus["STOPPED"] = 0] = "STOPPED";
    MediaStreamStatus[MediaStreamStatus["STARTED"] = 1] = "STARTED";
    MediaStreamStatus[MediaStreamStatus["DECODING"] = 2] = "DECODING";
    MediaStreamStatus[MediaStreamStatus["END"] = 3] = "END";
})(MediaStreamStatus = exports.MediaStreamStatus || (exports.MediaStreamStatus = {}));
class MediaStream {
    constructor(data) {
        this._status = MediaStreamStatus.STOPPED;
        if (data)
            this.setData(data);
    }
    setData(data) {
        Object.assign(this, data);
    }
    startConversion() {
        this.touch();
        return new Promise((resolve, reject) => {
            if (this._conversion) {
                resolve();
                return;
            }
            this._status = MediaStreamStatus.STARTED;
            const outputDir = `${Config_1.default.cacheRoot}/${this.id}`;
            const outputFile = `${outputDir}/index.m3u8`;
            if (!(0, fs_1.existsSync)(outputDir)) {
                (0, fs_1.mkdirSync)(outputDir, { recursive: true });
            }
            // const keyName = nanoid(8);
            // const keyFile = `${outputDir}/${keyName}`;
            // writeFileSync(keyFile, nanoid(16), 'utf8');
            // const keyInfoFile = `${outputDir}/.keyinfo`;
            // writeFileSync(
            //     keyInfoFile,
            //     `${Config.server.baseUrl}/media-stream/${this.id}/${keyName}\n` + `${keyFile}\n`,
            //     'utf8',
            // );
            // ffmpeg -i "rtsp://admin:admin123@172.31.252.202/cam/realmonitor?channel=1&subtype=0"
            // -c copy -f hls -hls_flags delete_segments test.m3u8
            let resolved = false;
            // ffmpeg -rtsp_transport tcp -i "rtsp"
            //  -fflags flush_packets -max_delay 1 -an -flags -global_header -hls_time 1 -hls_list_size 3 -vcodec copy -s 216x384 -b 1024k -y /tmp/rtsp-proxy/caches/1/index.m3u8
            const conversion = ffmpeg(this.url)
                .inputOptions(this.rtspTransport === 'tcp' ? ['-rtsp_transport tcp'] : [])
                .videoCodec("libx264")
                .format('hls')
                .outputOptions([
                '-hls_flags delete_segments',
                '-hls_start_number_source datetime',
                '-s 216x384',
                // '-b 1024k',
                // '-hls_flags delete_segments+append_list+omit_endlist',
                // '-hls_key_info_file', keyInfoFile,
            ])
                .output(outputFile)
                .on('start', (cmd) => {
                logger.info(`job[${this.id}] start:[${cmd}]`);
                if (!resolved) {
                    resolve();
                    resolved = true;
                }
            })
                .on('codecData', (data) => {
                logger.info(`job[${this.id}] codecData:[${JSON.stringify(data)}]`);
            })
                .on('progress', (progress) => {
                if (this._status !== MediaStreamStatus.DECODING && (0, fs_1.existsSync)(outputFile)) {
                    this._status = MediaStreamStatus.DECODING;
                }
                logger.debug(`job[${this.id}] progress:[${JSON.stringify(progress)}]`);
            })
                // .on('stderr', (output) => {
                //     logger.debug(output);
                // })
                // .on('error', (err, stdout, stderr) => {
                .on('error', (err) => {
                logger.info(`job[${this.id}] error: ${err.message} `);
                this._expiresAt = -1;
                streamsExpiresAt = 0;
                this._status = MediaStreamStatus.STOPPED;
                (0, fs_1.rmdirSync)(outputDir, { recursive: true });
                if (!resolved)
                    reject(err);
            })
                .on('end', () => {
                logger.info(`job[${this.id}] end`);
                this._expiresAt = -1;
                streamsExpiresAt = 0;
                this._status = MediaStreamStatus.END;
            });
            // 等待数据转换后文件出现
            this._conversion = conversion;
            this._conversion.run();
        });
    }
    stopConversion() {
        const conversion = this._conversion;
        if (conversion) {
            // logger.info(`in stopConversion`);
            conversion.kill('SIGKILL');
            setTimeout(() => {
                if (this._conversion === conversion) {
                    this._conversion = undefined;
                }
            }, 1000);
        }
    }
    touch() {
        if (this._expiresAt === undefined || this._expiresAt > 0) {
            this._expiresAt = Date.now() + CONVERSION_IDLE_TIMEOUT;
        }
        // logger.info('in touch', this.expires);
    }
    get status() {
        return this._status;
    }
    get expires() {
        return !Config_1.default.neverStop && this._expiresAt < Date.now();
    }
    static async findAll() {
        if (streamsExpiresAt < Date.now()) {
            const matrix = new Matrix_1.default();
            const result = await matrix.get({ path: 'v1/video-sources', query: { type: 'rtsp' } });
            const { items = [] } = Object.assign({}, result);
            await Promise.all(items.map(async (it) => {
                let stream = streams.get(it.id);
                if (stream && !stream.expires) {
                    stream.setData(it);
                }
                else {
                    it = (await MediaStream.parseURI(it)) || {};
                    stream = new MediaStream(it);
                    streams.set(stream.id, stream);
                }
                return stream;
            }));
            streamsExpiresAt = Date.now() + STREAMS_CACHE_TTL;
        }
        return streams;
    }
    static async findByPk(id) {
        const streams = await MediaStream.findAll();
        return streams.get(id);
    }
    // static get(data: any) {
    //     let stream = data && data.id && streams.get(data.id);
    //     if (stream) {
    //         stream.setData(data);
    //     } else {
    //         stream = new MediaStream(data);
    //         if (stream.id) streams.set(stream.id, stream);
    //     }
    //     return stream;
    // }
    static async parseURI(it) {
        var _a, _b, _c;
        const re = RegExp(`rtsp://(?<brand>\\w+)/(?<model>\\w+)/(?<indexCode>.+)`, 'i');
        const groups = (_a = it.url.match(re)) === null || _a === void 0 ? void 0 : _a.groups;
        switch (groups === null || groups === void 0 ? void 0 : groups.brand) {
            case 'hikvision':
                switch (groups === null || groups === void 0 ? void 0 : groups.model) {
                    case 'isc':
                        const key = it === null || it === void 0 ? void 0 : it.url;
                        if (!key) {
                            return it;
                        }
                        const hikvision = new Hikvision_1.default();
                        hikvision.setParams({
                            method: 'POST',
                            path: `artemis/api/video/v1/cameras/previewURLs`,
                        });
                        const ret = await hikvision.post(`artemis/api/video/v1/cameras/previewURLs`, {
                            cameraIndexCode: groups === null || groups === void 0 ? void 0 : groups.indexCode,
                            streamType: 0,
                            protocol: 'rtsp_s',
                        });
                        logger.info(`调用hik接口artemis/api/video/v1/cameras/previewURLs, params: ${JSON.stringify({
                            cameraIndexCode: groups === null || groups === void 0 ? void 0 : groups.indexCode,
                            streamType: 0,
                            protocol: 'rtsp_s',
                        })} 返回 ${JSON.stringify(ret)}`);
                        it.rtspTransport = 'tcp';
                        if ((_b = ret === null || ret === void 0 ? void 0 : ret.data) === null || _b === void 0 ? void 0 : _b.url) {
                            const newUrl = (_c = ret === null || ret === void 0 ? void 0 : ret.data) === null || _c === void 0 ? void 0 : _c.url.replace('[sms:preview]', '');
                            rtspUriHub.set(key, newUrl);
                            it.url = newUrl;
                        }
                        else if (rtspUriHub.has(key)) {
                            it.url = rtspUriHub.get(key);
                            logger.debug(`parseURI hit cache ${it.id}, ${key} ${it.url}`);
                        }
                        logger.info(`parseURI ${it.id}, ${it.url}`);
                        return it;
                }
                break;
        }
        return it;
    }
}
exports.default = MediaStream;
