function writeEBML(buffer, bufferFileOffset, ebml) {
  // Is the ebml an array of sibling elements?
  if (Array.isArray(ebml)) {
    for (let i = 0; i < ebml.length; i++) {
      writeEBML(buffer, bufferFileOffset, ebml[i]);
    }
    // Is this some sort of raw data that we want to write directly?
  } else if (typeof ebml === 'string') {
    buffer.writeString(ebml);
  } else if (ebml instanceof Uint8Array) {
    buffer.writeBytes(ebml);
  } else if (ebml.id) {
    // We're writing an EBML element
    ebml.offset = buffer.pos + bufferFileOffset;

    buffer.writeUnsignedIntBE(ebml.id);  // ID field

    // Now we need to write the size field, so we must know the payload size:

    if (Array.isArray(ebml.data)) {
      // Writing an array of child elements. We won't try to measure the size of
      // the children up-front

      let sizePos, dataBegin, dataEnd;

      if (ebml.size === -1) {
        // Write the reserved all-one-bits marker to note that the size of this
        // element is unknown/unbounded
        buffer.writeByte(0xFF);
      } else {
        sizePos = buffer.pos;

        /* Write a dummy size field to overwrite later. 4 bytes allows an
         * element maximum size of 256MB, which should be plenty (we don't want
         * to have to buffer that much data in memory at one time anyway!)
         */
        buffer.writeBytes([0, 0, 0, 0]);
      }

      dataBegin = buffer.pos;

      ebml.dataOffset = dataBegin + bufferFileOffset;
      writeEBML(buffer, bufferFileOffset, ebml.data);

      if (ebml.size !== -1) {
        dataEnd = buffer.pos;

        ebml.size = dataEnd - dataBegin;

        buffer.seek(sizePos);
        buffer.writeEBMLVarIntWidth(ebml.size, 4);  // Size field

        buffer.seek(dataEnd);
      }
    } else if (typeof ebml.data === 'string') {
      buffer.writeEBMLVarInt(ebml.data.length);  // Size field
      ebml.dataOffset = buffer.pos + bufferFileOffset;
      buffer.writeString(ebml.data);
    } else if (typeof ebml.data === 'number') {
      // Allow the caller to explicitly choose the size if they wish by
      // supplying a size field
      if (!ebml.size) {
        ebml.size = buffer.measureUnsignedInt(ebml.data);
      }

      buffer.writeEBMLVarInt(ebml.size);  // Size field
      ebml.dataOffset = buffer.pos + bufferFileOffset;
      buffer.writeUnsignedIntBE(ebml.data, ebml.size);
    } else if (ebml.data instanceof EBMLFloat64) {
      buffer.writeEBMLVarInt(8);  // Size field
      ebml.dataOffset = buffer.pos + bufferFileOffset;
      buffer.writeDoubleBE(ebml.data.value);
    } else if (ebml.data instanceof EBMLFloat32) {
      buffer.writeEBMLVarInt(4);  // Size field
      ebml.dataOffset = buffer.pos + bufferFileOffset;
      buffer.writeFloatBE(ebml.data.value);
    } else if (ebml.data instanceof Uint8Array) {
      buffer.writeEBMLVarInt(ebml.data.byteLength);  // Size field
      ebml.dataOffset = buffer.pos + bufferFileOffset;
      buffer.writeBytes(ebml.data);
    } else {
      throw new Error('Bad EBML datatype ' + typeof ebml.data);
    }
  } else {
    throw new Error('Bad EBML datatype ' + typeof ebml.data);
  }
}
Write the given EBML object to the provided ArrayBufferStream.

@param buffer
@param {Number} bufferFileOffset - The buffer's first byte is at this
    position inside the video file.
                                   This is used to complete offset and
dataOffset fields in each EBML structure, indicating the file offset of the
first byte of the EBML element and its data payload.
@param {*} ebml