UNPKG

jsmediatags

Version:
372 lines (299 loc) 13.1 kB
'use strict'; function _typeof(obj) { "@babel/helpers - typeof"; if (typeof Symbol === "function" && typeof Symbol.iterator === "symbol") { _typeof = function _typeof(obj) { return typeof obj; }; } else { _typeof = function _typeof(obj) { return obj && typeof Symbol === "function" && obj.constructor === Symbol && obj !== Symbol.prototype ? "symbol" : typeof obj; }; } return _typeof(obj); } function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } } function _defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } function _createClass(Constructor, protoProps, staticProps) { if (protoProps) _defineProperties(Constructor.prototype, protoProps); if (staticProps) _defineProperties(Constructor, staticProps); return Constructor; } function _inherits(subClass, superClass) { if (typeof superClass !== "function" && superClass !== null) { throw new TypeError("Super expression must either be null or a function"); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, writable: true, configurable: true } }); if (superClass) _setPrototypeOf(subClass, superClass); } function _setPrototypeOf(o, p) { _setPrototypeOf = Object.setPrototypeOf || function _setPrototypeOf(o, p) { o.__proto__ = p; return o; }; return _setPrototypeOf(o, p); } function _createSuper(Derived) { var hasNativeReflectConstruct = _isNativeReflectConstruct(); return function _createSuperInternal() { var Super = _getPrototypeOf(Derived), result; if (hasNativeReflectConstruct) { var NewTarget = _getPrototypeOf(this).constructor; result = Reflect.construct(Super, arguments, NewTarget); } else { result = Super.apply(this, arguments); } return _possibleConstructorReturn(this, result); }; } function _possibleConstructorReturn(self, call) { if (call && (_typeof(call) === "object" || typeof call === "function")) { return call; } return _assertThisInitialized(self); } function _assertThisInitialized(self) { if (self === void 0) { throw new ReferenceError("this hasn't been initialised - super() hasn't been called"); } return self; } function _isNativeReflectConstruct() { if (typeof Reflect === "undefined" || !Reflect.construct) return false; if (Reflect.construct.sham) return false; if (typeof Proxy === "function") return true; try { Boolean.prototype.valueOf.call(Reflect.construct(Boolean, [], function () {})); return true; } catch (e) { return false; } } function _getPrototypeOf(o) { _getPrototypeOf = Object.setPrototypeOf ? Object.getPrototypeOf : function _getPrototypeOf(o) { return o.__proto__ || Object.getPrototypeOf(o); }; return _getPrototypeOf(o); } function _defineProperty(obj, key, value) { if (key in obj) { Object.defineProperty(obj, key, { value: value, enumerable: true, configurable: true, writable: true }); } else { obj[key] = value; } return obj; } var ChunkedFileData = require('./ChunkedFileData'); var MediaFileReader = require('./MediaFileReader'); var CHUNK_SIZE = 1024; var XhrFileReader = /*#__PURE__*/function (_MediaFileReader) { _inherits(XhrFileReader, _MediaFileReader); var _super = _createSuper(XhrFileReader); function XhrFileReader(url) { var _this; _classCallCheck(this, XhrFileReader); _this = _super.call(this); _defineProperty(_assertThisInitialized(_this), "_url", void 0); _defineProperty(_assertThisInitialized(_this), "_fileData", void 0); _this._url = url; _this._fileData = new ChunkedFileData(); return _this; } _createClass(XhrFileReader, [{ key: "_init", value: function _init(callbacks) { if (XhrFileReader._config.avoidHeadRequests) { this._fetchSizeWithGetRequest(callbacks); } else { this._fetchSizeWithHeadRequest(callbacks); } } }, { key: "_fetchSizeWithHeadRequest", value: function _fetchSizeWithHeadRequest(callbacks) { var self = this; this._makeXHRRequest("HEAD", null, { onSuccess: function onSuccess(xhr) { var contentLength = self._parseContentLength(xhr); if (contentLength) { self._size = contentLength; callbacks.onSuccess(); } else { // Content-Length not provided by the server, fallback to // GET requests. self._fetchSizeWithGetRequest(callbacks); } }, onError: callbacks.onError }); } }, { key: "_fetchSizeWithGetRequest", value: function _fetchSizeWithGetRequest(callbacks) { var self = this; var range = this._roundRangeToChunkMultiple([0, 0]); this._makeXHRRequest("GET", range, { onSuccess: function onSuccess(xhr) { var contentRange = self._parseContentRange(xhr); var data = self._getXhrResponseContent(xhr); if (contentRange) { if (contentRange.instanceLength == null) { // Last resort, server is not able to tell us the content length, // need to fetch entire file then. self._fetchEntireFile(callbacks); return; } self._size = contentRange.instanceLength; } else { // Range request not supported, we got the entire file self._size = data.length; } self._fileData.addData(0, data); callbacks.onSuccess(); }, onError: callbacks.onError }); } }, { key: "_fetchEntireFile", value: function _fetchEntireFile(callbacks) { var self = this; this._makeXHRRequest("GET", null, { onSuccess: function onSuccess(xhr) { var data = self._getXhrResponseContent(xhr); self._size = data.length; self._fileData.addData(0, data); callbacks.onSuccess(); }, onError: callbacks.onError }); } }, { key: "_getXhrResponseContent", value: function _getXhrResponseContent(xhr) { return xhr.responseBody || xhr.responseText || ""; } }, { key: "_parseContentLength", value: function _parseContentLength(xhr) { var contentLength = this._getResponseHeader(xhr, "Content-Length"); if (contentLength == null) { return contentLength; } else { return parseInt(contentLength, 10); } } }, { key: "_parseContentRange", value: function _parseContentRange(xhr) { var contentRange = this._getResponseHeader(xhr, "Content-Range"); if (contentRange) { var parsedContentRange = contentRange.match(/bytes (\d+)-(\d+)\/(?:(\d+)|\*)/i); if (!parsedContentRange) { throw new Error("FIXME: Unknown Content-Range syntax: " + contentRange); } return { firstBytePosition: parseInt(parsedContentRange[1], 10), lastBytePosition: parseInt(parsedContentRange[2], 10), instanceLength: parsedContentRange[3] ? parseInt(parsedContentRange[3], 10) : null }; } else { return null; } } }, { key: "loadRange", value: function loadRange(range, callbacks) { var self = this; if (self._fileData.hasDataRange(range[0], Math.min(self._size, range[1]))) { setTimeout(callbacks.onSuccess, 1); return; } // Always download in multiples of CHUNK_SIZE. If we're going to make a // request might as well get a chunk that makes sense. The big cost is // establishing the connection so getting 10bytes or 1K doesn't really // make a difference. range = this._roundRangeToChunkMultiple(range); // Upper range should not be greater than max file size range[1] = Math.min(self._size, range[1]); this._makeXHRRequest("GET", range, { onSuccess: function onSuccess(xhr) { var data = self._getXhrResponseContent(xhr); self._fileData.addData(range[0], data); callbacks.onSuccess(); }, onError: callbacks.onError }); } }, { key: "_roundRangeToChunkMultiple", value: function _roundRangeToChunkMultiple(range) { var length = range[1] - range[0] + 1; var newLength = Math.ceil(length / CHUNK_SIZE) * CHUNK_SIZE; return [range[0], range[0] + newLength - 1]; } }, { key: "_makeXHRRequest", value: function _makeXHRRequest(method, range, callbacks) { var xhr = this._createXHRObject(); xhr.open(method, this._url); var onXHRLoad = function onXHRLoad() { // 200 - OK // 206 - Partial Content // $FlowIssue - xhr will not be null here if (xhr.status === 200 || xhr.status === 206) { callbacks.onSuccess(xhr); } else if (callbacks.onError) { callbacks.onError({ "type": "xhr", "info": "Unexpected HTTP status " + xhr.status + ".", "xhr": xhr }); } xhr = null; }; if (typeof xhr.onload !== 'undefined') { xhr.onload = onXHRLoad; xhr.onerror = function () { if (callbacks.onError) { callbacks.onError({ "type": "xhr", "info": "Generic XHR error, check xhr object.", "xhr": xhr }); } }; } else { xhr.onreadystatechange = function () { // $FlowIssue - xhr will not be null here if (xhr.readyState === 4) { onXHRLoad(); } }; } if (XhrFileReader._config.timeoutInSec) { xhr.timeout = XhrFileReader._config.timeoutInSec * 1000; xhr.ontimeout = function () { if (callbacks.onError) { callbacks.onError({ "type": "xhr", // $FlowIssue - xhr.timeout will not be null "info": "Timeout after " + xhr.timeout / 1000 + "s. Use jsmediatags.Config.setXhrTimeout to override.", "xhr": xhr }); } }; } xhr.overrideMimeType("text/plain; charset=x-user-defined"); if (range) { this._setRequestHeader(xhr, "Range", "bytes=" + range[0] + "-" + range[1]); } this._setRequestHeader(xhr, "If-Modified-Since", "Sat, 01 Jan 1970 00:00:00 GMT"); xhr.send(null); } }, { key: "_setRequestHeader", value: function _setRequestHeader(xhr, headerName, headerValue) { if (XhrFileReader._config.disallowedXhrHeaders.indexOf(headerName.toLowerCase()) < 0) { xhr.setRequestHeader(headerName, headerValue); } } }, { key: "_hasResponseHeader", value: function _hasResponseHeader(xhr, headerName) { var allResponseHeaders = xhr.getAllResponseHeaders(); if (!allResponseHeaders) { return false; } var headers = allResponseHeaders.split("\r\n"); var headerNames = []; for (var i = 0; i < headers.length; i++) { headerNames[i] = headers[i].split(":")[0].toLowerCase(); } return headerNames.indexOf(headerName.toLowerCase()) >= 0; } }, { key: "_getResponseHeader", value: function _getResponseHeader(xhr, headerName) { if (!this._hasResponseHeader(xhr, headerName)) { return null; } return xhr.getResponseHeader(headerName); } }, { key: "getByteAt", value: function getByteAt(offset) { var character = this._fileData.getByteAt(offset); return character.charCodeAt(0) & 0xff; } }, { key: "_isWebWorker", value: function _isWebWorker() { return typeof WorkerGlobalScope !== 'undefined' && self instanceof WorkerGlobalScope; } }, { key: "_createXHRObject", value: function _createXHRObject() { if (typeof window === "undefined" && !this._isWebWorker()) { // $FlowIssue - flow is not able to recognize this module. return new (require("xhr2").XMLHttpRequest)(); } if (typeof XMLHttpRequest !== "undefined") { return new XMLHttpRequest(); } throw new Error("XMLHttpRequest is not supported"); } }], [{ key: "canReadFile", value: function canReadFile(file) { return typeof file === 'string' && /^[a-z]+:\/\//i.test(file); } }, { key: "setConfig", value: function setConfig(config) { for (var key in config) { if (config.hasOwnProperty(key)) { this._config[key] = config[key]; } } var disallowedXhrHeaders = this._config.disallowedXhrHeaders; for (var i = 0; i < disallowedXhrHeaders.length; i++) { disallowedXhrHeaders[i] = disallowedXhrHeaders[i].toLowerCase(); } } }]); return XhrFileReader; }(MediaFileReader); _defineProperty(XhrFileReader, "_config", void 0); XhrFileReader._config = { avoidHeadRequests: false, disallowedXhrHeaders: [], timeoutInSec: 30 }; module.exports = XhrFileReader;