UNPKG

mtags

Version:

Media file tag parser

414 lines (373 loc) 13 kB
/** * Buffered Binary Ajax 0.2.1 * Copyright (c) 2010 António Afonso, antonio.afonso gmail, http://www.aadsm.net/ * MIT License [http://www.opensource.org/licenses/mit-license.php] * * Adapted from Binary Ajax 0.1.5 */ /** * This function prepares a BufferedBinaryFile object for reading the file pointed by the URL given. * * @param {String} strUrl The URL with the location of the file to be read. * @param {function(BufferedBinaryFile)} fncCallback The function that will be invoked when the BufferedBinaryFile is ready to be used. * @param {function()} fncError The function that will be invoked when an error occrus, for instance, the file pointed by the URL is doesn't exist. */ var StringUtils = require('../utils/stringutils'); exports.BufferedBinaryAjax = function (strUrl, fncCallback, fncError) { function sendRequest(strURL, fncCallback, fncError, aRange, bAcceptRanges, iFileSize, bAsync) { var oHTTP = createRequest(); if (oHTTP) { var iDataOffset = 0; if (aRange && !bAcceptRanges) { iDataOffset = aRange[0]; } var iDataLen = 0; if (aRange) { iDataLen = aRange[1]-aRange[0]+1; } if( typeof bAsync === "undefined" ) bAsync = true; if (fncCallback) { if (typeof(oHTTP.onload) != "undefined") { oHTTP.onload = function() { if (oHTTP.status == "200" || oHTTP.status == "206") { oHTTP.fileSize = iFileSize || oHTTP.getResponseHeader("Content-Length"); fncCallback(oHTTP); } else { if (fncError) fncError(); } oHTTP = null; }; } else { oHTTP.onreadystatechange = function() { if (oHTTP.readyState == 4) { if (oHTTP.status == "200" || oHTTP.status == "206") { oHTTP.fileSize = iFileSize || oHTTP.getResponseHeader("Content-Length"); fncCallback(oHTTP); } else { if (fncError) fncError(); } oHTTP = null; } }; } } oHTTP.open("GET", strURL, bAsync); if (oHTTP.overrideMimeType) oHTTP.overrideMimeType('text/plain; charset=x-user-defined'); if (aRange && bAcceptRanges) { oHTTP.setRequestHeader("Range", "bytes=" + aRange[0] + "-" + aRange[1]); } oHTTP.setRequestHeader("If-Modified-Since", "Sat, 1 Jan 1970 00:00:00 GMT"); oHTTP.send(null); } else { if (fncError) fncError(); } } function createRequest() { var oHTTP = null; if (window.XMLHttpRequest) { oHTTP = new XMLHttpRequest(); } else if (window.ActiveXObject) { oHTTP = new ActiveXObject("Microsoft.XMLHTTP"); } return oHTTP; } function getHead(strURL, fncCallback, fncError) { var oHTTP = createRequest(); if (oHTTP) { if (fncCallback) { if (typeof(oHTTP.onload) != "undefined") { oHTTP.onload = function() { if (oHTTP.status == "200") { fncCallback(this); } else { if (fncError) fncError(); } oHTTP = null; }; } else { oHTTP.onreadystatechange = function() { if (oHTTP.readyState == 4) { if (oHTTP.status == "200") { fncCallback(this); } else { if (fncError) fncError(); } oHTTP = null; } }; } } oHTTP.open("HEAD", strURL, true); oHTTP.send(null); } else { if (fncError) fncError(); } } /** * @class Reads a remote file without having to download it all. * * Creates a new BufferedBinaryFile that will download chunks of the file pointed by the URL given only on a per need basis. * * @param {string} strUrl The URL with the location of the file to be read. * @param {number} iLength The size of the file. * @param {number} [blockSize=2048] The size of the chunk that will be downloaded when data is read. * @param {number} [blockRadius=0] The number of chunks, immediately after and before the chunk needed, that will also be downloaded. * * @constructor * @augments BinaryFile */ function BufferedBinaryFile(strUrl, iLength, blockSize, blockRadius) { var undefined; var downloadedBytesCount = 0; var binaryFile = new BinaryFile("", 0, iLength); var blocks = []; blockSize = blockSize || 1024*2; blockRadius = (typeof blockRadius === "undefined") ? 0 : blockRadius; blockTotal = ~~((iLength-1)/blockSize) + 1; function getBlockRangeForByteRange(range) { var blockStart = ~~(range[0]/blockSize) - blockRadius; var blockEnd = ~~(range[1]/blockSize)+1 + blockRadius; if( blockStart < 0 ) blockStart = 0; if( blockEnd >= blockTotal ) blockEnd = blockTotal-1; return [blockStart, blockEnd]; } // TODO: wondering if a "recently used block" could help things around // here. function getBlockAtOffset(offset) { var blockRange = getBlockRangeForByteRange([offset, offset]); waitForBlocks(blockRange); return blocks[~~(offset/blockSize)]; } /** * @param {?function()} callback If a function is passed then this function will be asynchronous and the callback invoked when the blocks have been loaded, otherwise it blocks script execution until the request is completed. */ function waitForBlocks(blockRange, callback) { // Filter out already downloaded blocks or return if found out that // the entire block range has already been downloaded. while( blocks[blockRange[0]] ) { blockRange[0]++; if( blockRange[0] > blockRange[1] ) return callback ? callback() : undefined; } while( blocks[blockRange[1]] ) { blockRange[1]--; if( blockRange[0] > blockRange[1] ) return callback ? callback() : undefined; } var range = [blockRange[0]*blockSize, (blockRange[1]+1)*blockSize-1]; //console.log("Getting: " + range[0] + " to " + range[1]); sendRequest( strUrl, function(http) { var size = parseInt(http.getResponseHeader("Content-Length"), 10); // Range header not supported if( size == iLength ) { blockRange[0] = 0; blockRange[1] = blockTotal-1; range[0] = 0; range[1] = iLength-1; } var block = { data: http.responseBody || http.responseText, offset: range[0] }; for( var i = blockRange[0]; i <= blockRange[1]; i++ ) { blocks[i] = block; } downloadedBytesCount += range[1] - range[0] + 1; if (callback) callback(); }, fncError, range, "bytes", undefined, !!callback ); } // Mixin all BinaryFile's methods. // Not using prototype linking since the constructor needs to know // the length of the file. for( var key in binaryFile ) { if( binaryFile.hasOwnProperty(key) && typeof binaryFile[key] === "function") { this[key] = binaryFile[key]; } } /** * @override */ this.getByteAt = function(iOffset) { var block = getBlockAtOffset(iOffset); if( typeof block.data == "string" ) { return block.data.charCodeAt(iOffset - block.offset) & 0xFF; } else if( typeof block.data == "unknown" ) { return IEBinary_getByteAt(block.data, iOffset - block.offset); } }; /** * Gets the number of total bytes that have been downloaded. * * @returns The number of total bytes that have been downloaded. */ this.getDownloadedBytesCount = function() { return downloadedBytesCount; }; /** * Downloads the byte range given. Useful for preloading. * * @param {Array} range Two element array that denotes the first byte to be read on the first position and the last byte to be read on the last position. A range of [2, 5] will download bytes 2,3,4 and 5. * @param {?function()} callback The function to invoke when the blocks have been downloaded, this makes this call asynchronous. */ this.loadRange = function(range, callback) { var blockRange = getBlockRangeForByteRange(range); waitForBlocks(blockRange, callback); }; } function init() { getHead( strUrl, function(oHTTP) { var iLength = parseInt(oHTTP.getResponseHeader("Content-Length"),10) || -1; fncCallback(new BufferedBinaryFile(strUrl, iLength)); } ); } init(); }; /** * @constructor */ function BinaryFile(strData, iDataOffset, iDataLength) { var data = strData; var dataOffset = iDataOffset || 0; var dataLength = 0; this.getRawData = function() { return data; }; if (typeof strData == "string") { dataLength = iDataLength || data.length; this.getByteAt = function(iOffset) { return data.charCodeAt(iOffset + dataOffset) & 0xFF; }; } else if (typeof strData == "unknown") { dataLength = iDataLength || IEBinary_getLength(data); this.getByteAt = function(iOffset) { return IEBinary_getByteAt(data, iOffset + dataOffset); }; } // @aadsm this.getBytesAt = function(iOffset, iLength) { var bytes = new Array(iLength); for( var i = 0; i < iLength; i++ ) { bytes[i] = this.getByteAt(iOffset+i); } return bytes; }; this.getLength = function() { return dataLength; }; // @aadsm this.isBitSetAt = function(iOffset, iBit) { var iByte = this.getByteAt(iOffset); return (iByte & (1 << iBit)) != 0; }; this.getSByteAt = function(iOffset) { var iByte = this.getByteAt(iOffset); if (iByte > 127) return iByte - 256; else return iByte; }; this.getShortAt = function(iOffset, bBigEndian) { var iShort = bBigEndian ? (this.getByteAt(iOffset) << 8) + this.getByteAt(iOffset + 1) : (this.getByteAt(iOffset + 1) << 8) + this.getByteAt(iOffset); if (iShort < 0) iShort += 65536; return iShort; }; this.getSShortAt = function(iOffset, bBigEndian) { var iUShort = this.getShortAt(iOffset, bBigEndian); if (iUShort > 32767) return iUShort - 65536; else return iUShort; }; this.getLongAt = function(iOffset, bBigEndian) { var iByte1 = this.getByteAt(iOffset), iByte2 = this.getByteAt(iOffset + 1), iByte3 = this.getByteAt(iOffset + 2), iByte4 = this.getByteAt(iOffset + 3); var iLong = bBigEndian ? (((((iByte1 << 8) + iByte2) << 8) + iByte3) << 8) + iByte4 : (((((iByte4 << 8) + iByte3) << 8) + iByte2) << 8) + iByte1; if (iLong < 0) iLong += 4294967296; return iLong; }; this.getSLongAt = function(iOffset, bBigEndian) { var iULong = this.getLongAt(iOffset, bBigEndian); if (iULong > 2147483647) return iULong - 4294967296; else return iULong; }; // @aadsm this.getInteger24At = function(iOffset, bBigEndian) { var iByte1 = this.getByteAt(iOffset), iByte2 = this.getByteAt(iOffset + 1), iByte3 = this.getByteAt(iOffset + 2); var iInteger = bBigEndian ? ((((iByte1 << 8) + iByte2) << 8) + iByte3) : ((((iByte3 << 8) + iByte2) << 8) + iByte1); if (iInteger < 0) iInteger += 16777216; return iInteger; }; this.getStringAt = function(iOffset, iLength) { var aStr = []; for (var i=iOffset,j=0;i<iOffset+iLength;i++,j++) { aStr[j] = String.fromCharCode(this.getByteAt(i)); } return aStr.join(""); }; // @aadsm this.getStringWithCharsetAt = function(iOffset, iLength, iCharset) { var bytes = this.getBytesAt(iOffset, iLength); var sString; switch( iCharset.toLowerCase() ) { case 'utf-16': case 'utf-16le': case 'utf-16be': sString = StringUtils.readUTF16String(bytes, iCharset); break; case 'utf-8': sString = StringUtils.readUTF8String(bytes); break; default: sString = StringUtils.readNullTerminatedString(bytes); break; } return sString; }; this.getCharAt = function(iOffset) { return String.fromCharCode(this.getByteAt(iOffset)); }; this.toBase64 = function() { return window.btoa(data); }; this.fromBase64 = function(strBase64) { data = window.atob(strBase64); }; this.loadRange = function(range, callback) { callback(); }; } exports.BinaryFile = BinaryFile; /* document.write( "<script type='text/vbscript'>\r\n" + "Function IEBinary_getByteAt(strBinary, iOffset)\r\n" + " IEBinary_getByteAt = AscB(MidB(strBinary,iOffset+1,1))\r\n" + "End Function\r\n" + "Function IEBinary_getLength(strBinary)\r\n" + " IEBinary_getLength = LenB(strBinary)\r\n" + "End Function\r\n" + "</script>\r\n" ); */