gkozlenko/node-video-lib

View on GitHub
lib/flv/amf-parser.js

Summary

Maintainability
C
1 day
Test Coverage
D
63%
'use strict';

const TYPE_NUMBER            = 0x00;
const TYPE_BOOLEAN           = 0x01;
const TYPE_STRING            = 0x02;
const TYPE_OBJECT            = 0x03;
// const TYPE_MOVIECLIP         = 0x04; // reserved, not supported
const TYPE_NULL              = 0x05;
const TYPE_UNDEFINED         = 0x06;
const TYPE_REFERENCE         = 0x07;
const TYPE_ECMA_ARRAY        = 0x08;
const TYPE_OBJECT_END        = 0x09;
const TYPE_STRICT_ARRAY      = 0x0a;
const TYPE_DATE              = 0x0b;
const TYPE_LONG_STRING       = 0x0c;
const TYPE_UNSUPPORTED       = 0x0d;
// const TYPE_RECORDSET         = 0x0e; // reserved, not supported
const TYPE_XML_DOCUMENT      = 0x0f;
// const TYPE_TYPED_OBJECT      = 0x10; // not implemented
// const TYPE_AVMPLUS_OBJECT    = 0x11; // not implemented

class AmfReader {

    constructor(buffer) {
        this.buffer = buffer;
        this.pos = 0;
    }

    read() {
        let data = [];
        while (this.pos < this.buffer.length) {
            data.push(this._readByType(this._readByte()));
        }
        return data;
    }

    _readByType(type) {
        switch (type) {
            case TYPE_NUMBER: {
                return this._readDouble();
            }
            case TYPE_BOOLEAN: {
                return this._readBoolean();
            }
            case TYPE_STRING: {
                return this._readString();
            }
            case TYPE_OBJECT: {
                return this._readObject();
            }
            case TYPE_NULL: {
                return null;
            }
            case TYPE_UNDEFINED: {
                return undefined;
            }
            case TYPE_UNSUPPORTED: {
                return null;
            }
            case TYPE_REFERENCE: {
                return this._readReference();
            }
            case TYPE_ECMA_ARRAY: {
                return this._readEcmaArray();
            }
            case TYPE_STRICT_ARRAY: {
                return this._readStrictArray();
            }
            case TYPE_DATE: {
                return this._readDate();
            }
            case TYPE_LONG_STRING: {
                return this._readLongString();
            }
            case TYPE_XML_DOCUMENT: {
                return this._readXmlDocument();
            }
        }
    }

    _readByte() {
        return this.buffer[this.pos++];
    }

    _readDouble() {
        let value = this.buffer.readDoubleBE(this.pos);
        this.pos += 8;
        return value;
    }

    _readBoolean() {
        return 0 !== this._readByte();
    }

    _readString() {
        let size = this.buffer.readUInt16BE(this.pos);
        this.pos += 2;
        let value = this.buffer.toString('utf8', this.pos, this.pos + size);
        this.pos += size;
        return value;
    }

    _readObject() {
        let object = {};
        let key, type;
        do {
            key = this._readString();
            type = this._readByte();
            if (type !== TYPE_OBJECT_END) {
                object[key] = this._readByType(type);
            }
        } while (type !== TYPE_OBJECT_END);
        return object;
    }

    _readReference() {
        let index = this.buffer.readUInt16BE(this.pos);
        this.pos += 2;
        return `Reference #${index}`;
    }

    _readEcmaArray() {
        this.pos += 4;
        return this._readObject();
    }

    _readStrictArray() {
        let size = this.buffer.readUInt32BE(this.pos);
        this.pos += 4;
        let array = [];
        for (let i = 0; i < size; i++) {
            array.push(this._readByType(this._readByte()));
        }
        return array;
    }

    _readDate() {
        let value = this.buffer.readDoubleBE(this.pos + 2);
        this.pos += 10;
        return value;
    }

    _readLongString() {
        let size = this.buffer.readUInt32BE(this.pos);
        this.pos += 4;
        let value = this.buffer.toString('utf8', this.pos, this.pos + size);
        this.pos += size;
        return value;
    }

    _readXmlDocument() {
        return this._readLongString();
    }

}

class AmfParser {

    static parse(buffer) {
        let reader = new AmfReader(buffer);
        return reader.read();
    }

}

module.exports = AmfParser;