| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290 | /* global $, $iq */
import { getLogger } from "jitsi-meet-logger";
const logger = getLogger(__filename);
var XMPPEvents = require("../../service/xmpp/XMPPEvents");
var JitsiRecorderErrors = require("../../JitsiRecorderErrors");
var GlobalOnErrorHandler = require("../util/GlobalOnErrorHandler");
function Recording(type, eventEmitter, connection, focusMucJid, jirecon,
    roomjid) {
    this.eventEmitter = eventEmitter;
    this.connection = connection;
    this.state = null;
    this.focusMucJid = focusMucJid;
    this.jirecon = jirecon;
    this.url = null;
    this.type = type;
    this._isSupported
        = ( type === Recording.types.JIRECON && !this.jirecon
            || (type !== Recording.types.JIBRI
                && type !== Recording.types.COLIBRI))
            ? false : true;
    /**
     * The ID of the jirecon recording session. Jirecon generates it when we
     * initially start recording, and it needs to be used in subsequent requests
     * to jirecon.
     */
    this.jireconRid = null;
    this.roomjid = roomjid;
}
Recording.types = {
    COLIBRI: "colibri",
    JIRECON: "jirecon",
    JIBRI: "jibri"
};
Recording.status = {
    ON: "on",
    OFF: "off",
    AVAILABLE: "available",
    UNAVAILABLE: "unavailable",
    PENDING: "pending",
    RETRYING: "retrying",
    BUSY: "busy",
    FAILED: "failed"
};
Recording.action = {
    START: "start",
    STOP: "stop"
};
Recording.prototype.handleJibriPresence = function (jibri) {
    var attributes = jibri.attributes;
    if(!attributes)
        return;
    var newState = attributes.status;
    logger.log("Handle jibri presence : ", newState);
    if (newState === this.state)
        return;
    if (newState === "undefined") {
        this.state = Recording.status.UNAVAILABLE;
    }
    else if (newState === "off") {
        if (!this.state
            || this.state === "undefined"
            || this.state === Recording.status.UNAVAILABLE)
            this.state = Recording.status.AVAILABLE;
        else
            this.state = Recording.status.OFF;
    }
    else {
        this.state = newState;
    }
    this.eventEmitter.emit(XMPPEvents.RECORDER_STATE_CHANGED, this.state);
};
Recording.prototype.setRecordingJibri
    = function (state, callback, errCallback, options) {
    if (state == this.state){
        errCallback(JitsiRecorderErrors.INVALID_STATE);
    }
    options = options || {};
    // FIXME jibri does not accept IQ without 'url' attribute set ?
    var iq = $iq({to: this.focusMucJid, type: 'set'})
        .c('jibri', {
        "xmlns": 'http://jitsi.org/protocol/jibri',
        "action": (state === Recording.status.ON)
                    ? Recording.action.START
                    : Recording.action.STOP,
        "streamid": options.streamId,
        }).up();
    logger.log('Set jibri recording: ' + state, iq.nodeTree);
    logger.log(iq.nodeTree);
    this.connection.sendIQ(
        iq,
        function (result) {
            logger.log("Result", result);
            callback($(result).find('jibri').attr('state'),
            $(result).find('jibri').attr('url'));
        },
        function (error) {
            logger.log('Failed to start recording, error: ', error);
            errCallback(error);
        });
};
Recording.prototype.setRecordingJirecon =
    function (state, callback, errCallback) {
    if (state == this.state){
        errCallback(new Error("Invalid state!"));
    }
    var iq = $iq({to: this.jirecon, type: 'set'})
        .c('recording', {xmlns: 'http://jitsi.org/protocol/jirecon',
            action: (state === Recording.status.ON)
                ? Recording.action.START
                : Recording.action.STOP,
            mucjid: this.roomjid});
    if (state === 'off'){
        iq.attrs({rid: this.jireconRid});
    }
    logger.log('Start recording');
    var self = this;
    this.connection.sendIQ(
        iq,
        function (result) {
            // TODO wait for an IQ with the real status, since this is
            // provisional?
            self.jireconRid = $(result).find('recording').attr('rid');
            logger.log('Recording ' +
                ((state === Recording.status.ON) ? 'started' : 'stopped') +
                '(jirecon)' + result);
            self.state = state;
            if (state === Recording.status.OFF){
                self.jireconRid = null;
            }
            callback(state);
        },
        function (error) {
            logger.log('Failed to start recording, error: ', error);
            errCallback(error);
        });
};
// Sends a COLIBRI message which enables or disables (according to 'state')
// the recording on the bridge. Waits for the result IQ and calls 'callback'
// with the new recording state, according to the IQ.
Recording.prototype.setRecordingColibri =
function (state, callback, errCallback, options) {
    var elem = $iq({to: this.focusMucJid, type: 'set'});
    elem.c('conference', {
        xmlns: 'http://jitsi.org/protocol/colibri'
    });
    elem.c('recording', {state: state, token: options.token});
    var self = this;
    this.connection.sendIQ(elem,
        function (result) {
            logger.log('Set recording "', state, '". Result:', result);
            var recordingElem = $(result).find('>conference>recording');
            var newState = recordingElem.attr('state');
            self.state = newState;
            callback(newState);
            if (newState === 'pending') {
                self.connection.addHandler(function(iq){
                    var state = $(iq).find('recording').attr('state');
                    if (state) {
                        self.state = newState;
                        callback(state);
                    }
                }, 'http://jitsi.org/protocol/colibri', 'iq', null, null, null);
            }
        },
        function (error) {
            logger.warn(error);
            errCallback(error);
        }
    );
};
Recording.prototype.setRecording =
function (state, callback, errCallback, options) {
    switch(this.type){
        case Recording.types.JIRECON:
            this.setRecordingJirecon(state, callback, errCallback, options);
            break;
        case Recording.types.COLIBRI:
            this.setRecordingColibri(state, callback, errCallback, options);
            break;
        case Recording.types.JIBRI:
            this.setRecordingJibri(state, callback, errCallback, options);
            break;
        default:
            var errmsg = "Unknown recording type!";
            GlobalOnErrorHandler.callErrorHandler(new Error(errmsg));
            logger.error(errmsg);
            return;
    }
};
/**
 * Starts/stops the recording.
 * @param token token for authentication
 * @param statusChangeHandler {function} receives the new status as argument.
 */
Recording.prototype.toggleRecording = function (options, statusChangeHandler) {
    var oldState = this.state;
    // If the recorder is currently unavailable we throw an error.
    if (oldState === Recording.status.UNAVAILABLE
        || oldState === Recording.status.FAILED)
        statusChangeHandler(Recording.status.FAILED,
                            JitsiRecorderErrors.RECORDER_UNAVAILABLE);
    else if (oldState === Recording.status.BUSY)
        statusChangeHandler(Recording.status.BUSY,
                            JitsiRecorderErrors.RECORDER_BUSY);
    // If we're about to turn ON the recording we need either a streamId or
    // an authentication token depending on the recording type. If we don't
    // have any of those we throw an error.
    if ((oldState === Recording.status.OFF
        || oldState === Recording.status.AVAILABLE)
        && ((!options.token && this.type === Recording.types.COLIBRI) ||
        (!options.streamId && this.type === Recording.types.JIBRI))) {
        statusChangeHandler(Recording.status.FAILED,
                            JitsiRecorderErrors.NO_TOKEN);
        logger.error("No token passed!");
        return;
    }
    var newState = (oldState === Recording.status.AVAILABLE
                    || oldState === Recording.status.OFF)
                    ? Recording.status.ON
                    : Recording.status.OFF;
    var self = this;
    logger.log("Toggle recording (old state, new state): ", oldState, newState);
    this.setRecording(newState,
        function (state, url) {
            // If the state is undefined we're going to wait for presence
            // update.
            if (state && state !== oldState) {
                self.state = state;
                self.url = url;
                statusChangeHandler(state);
            }
        }, function (error) {
            statusChangeHandler(Recording.status.FAILED, error);
        }, options);
};
/**
 * Returns true if the recording is supproted and false if not.
 */
Recording.prototype.isSupported = function () {
    return this._isSupported;
};
/**
 * Returns null if the recording is not supported, "on" if the recording started
 * and "off" if the recording is not started.
 */
Recording.prototype.getState = function () {
    return this.state;
};
/**
 * Returns the url of the recorded video.
 */
Recording.prototype.getURL = function () {
    return this.url;
};
module.exports = Recording;
 |