import 'dart:math'; import 'dart:typed_data'; import 'package:dio/dio.dart'; //Raised when data is not ready. class NotReadyException implements Exception {} //Raised when errro happened during the downloading. class DownloadErrorException implements Exception { String _message; String get message => _message; DownloadErrorException(this._message); } typedef DownloadCallback = void Function(double progress, DownloadErrorException? error); class VidUsDataHttpReader { late Uint8List _underlyingData = Uint8List(0); late int _bufferSize = 0; late int _index; late bool _error; late String _errorMsg; late String _url; late int _minChunkSize; late Dio _dio; late double _downloadProgress = 0; DownloadCallback? downloadCallback; VidUsDataHttpReader(String url, {this.downloadCallback, int minChunkSize = 65536, int connectTimeout = 30000, int receiveTimeout = 30000}) { _minChunkSize = minChunkSize; _url = url; _error = false; _errorMsg = ''; _index = 0; _dio = Dio(); _dio.options.connectTimeout = connectTimeout; _dio.options.receiveTimeout = receiveTimeout; _startDownload(); } ///Return the internal underlying data. Uint8List toBytes() { return _underlyingData; } ///Start the download in async way. void _startDownload() async { try { //get file info var fileSize = await _getFileSize(); if (!_error) { _underlyingData = Uint8List(fileSize); if (fileSize < _minChunkSize) { await _downloadChunk(0, fileSize - 1); if (!_error && downloadCallback != null) { _downloadProgress = _bufferSize / fileSize; downloadCallback!(_downloadProgress, null); }else{ throw DownloadErrorException(_errorMsg); } } else { var chunkSize = max(_minChunkSize, fileSize~/100) ; var last = fileSize % chunkSize; var chunkCount = fileSize ~/ chunkSize; for (var i = 0; i < chunkCount; i++) { var start = i * chunkSize; var end = start + chunkSize - 1; await _downloadChunk(start, end); if (_error) { throw DownloadErrorException(_errorMsg); } if (downloadCallback != null) { _downloadProgress = _bufferSize / fileSize; downloadCallback!(_downloadProgress, null); } } if (!_error && last > 0) { var start = chunkCount * chunkSize; var end = start + last - 1; await _downloadChunk(start, end); if (!_error && downloadCallback != null) { _downloadProgress = _bufferSize / fileSize; downloadCallback!(_downloadProgress, null); }else{ throw DownloadErrorException(_errorMsg); } } } } } on DownloadErrorException catch (e) { if (!_error && downloadCallback != null) { downloadCallback!(_downloadProgress, e); } } on Exception catch (e) { _error = true; _errorMsg = e.toString(); if (!_error && downloadCallback != null) { downloadCallback!(_downloadProgress, DownloadErrorException(_errorMsg)); } } } Future _getFileSize() async { var response = await _dio.head(_url); if (response.statusCode != 200) { _error = true; _errorMsg = 'Get file info error.'; return 0; } else { var contentLength = response.headers.value(Headers.contentLengthHeader); if (contentLength == null) { _error = true; _errorMsg = 'No Content-Length from headers.'; return 0; } else { var fileSize = int.parse(contentLength); return fileSize; } } } Future _downloadChunk(int chunkStart, int chunkEnd) async { var response = await _dio.get>(_url, options: Options(responseType: ResponseType.bytes, headers: {"range": "bytes=$chunkStart-$chunkEnd"})); if (response.statusCode == 206) { var downloadedData = response.data; if (downloadedData == null) { _error = true; _errorMsg = 'Download chunk error.'; } else { //Update the buffer. _underlyingData.setAll(_bufferSize, downloadedData); _bufferSize += downloadedData.length; } } else { _error = true; _errorMsg = 'Target url does not support download with chunks.'; } } //Close the reader and its http connection. void close() { _dio.close(force: true); } ///Read int value from binary data(little endian), int readInt([int index = -1]) { if (_error) { throw DownloadErrorException(_errorMsg); } if (index == -1) { if ((_bufferSize - _index) < 4) { throw NotReadyException(); } var intData = _underlyingData.buffer.asByteData(_index, 4); _index += 4; return intData.getInt32(0, Endian.little); } else { if ((_bufferSize - index) < 4) { throw NotReadyException(); } var intData = _underlyingData.buffer.asByteData(index, 4); _index = index + 4; return intData.getInt32(0, Endian.little); } } ///Read string from the binary data, the string format is utf-16, which is unicode in C# String readString([int index = -1]) { if (_error) { throw DownloadErrorException(_errorMsg); } int dataLength; if (index == -1) { dataLength = readInt(); if ((_bufferSize - _index) < dataLength) { throw NotReadyException(); } } else { dataLength = readInt(index); if ((_bufferSize - index) < dataLength) { throw NotReadyException(); } } var stringData = _underlyingData.buffer.asUint8List(_index, dataLength); _index += dataLength; var unicodeStringData = Uint8List.fromList(stringData.toList()).buffer.asUint16List(); return String.fromCharCodes(unicodeStringData); } ///Read int16 value from binary data(little endian) int readInt16([int index = -1]) { if (_error) { throw DownloadErrorException(_errorMsg); } if (index == -1) { if ((_bufferSize - _index) < 2) { throw NotReadyException(); } var intData = _underlyingData.buffer.asByteData(_index, 2); _index += 2; return intData.getInt16(0, Endian.little); } else { if ((_bufferSize - index) < 2) { throw NotReadyException(); } var intData = _underlyingData.buffer.asByteData(index, 2); _index = index + 2; return intData.getInt16(0, Endian.little); } } ///Read int64 value from binary data(little endian) ///this method is not support in web platform use readInt64V2 instead. int readInt64([int index = -1]) { if (_error) { throw DownloadErrorException(_errorMsg); } if (index == -1) { if ((_bufferSize - _index) < 8) { throw NotReadyException(); } var intData = _underlyingData.buffer.asByteData(_index, 8); _index += 8; return intData.getInt64(0, Endian.little); } else { if ((_bufferSize - index) < 8) { throw NotReadyException(); } var intData = _underlyingData.buffer.asByteData(index, 8); _index = index + 8; return intData.getInt64(0, Endian.little); } } ///Read int64 value from binary data(little endian) ///this method use two int32 to support read int64 on web platform int readInt64V2([int index = -1]) { if (_error) { throw DownloadErrorException(_errorMsg); } if (index == -1) { if ((_bufferSize - _index) < 8) { throw NotReadyException(); } int low = readInt(); int high = readInt(); int value = high >> 32; value |= low; return value; } else { if ((_bufferSize - index) < 8) { throw NotReadyException(); } int low = readInt(index); //Read the next int. int high = readInt(); int value = high >> 32; value |= low; return value; } } ///Read float value from binary data(little endian) double readFloat([int index = -1]) { if (_error) { throw DownloadErrorException(_errorMsg); } if (index == -1) { if ((_bufferSize - _index) < 4) { throw NotReadyException(); } var floatData = _underlyingData.buffer.asByteData(_index, 4); _index += 4; return floatData.getFloat32(0, Endian.little); } else { if ((_bufferSize - index) < 4) { throw NotReadyException(); } var floatData = _underlyingData.buffer.asByteData(index, 4); _index = index + 4; return floatData.getFloat32(0, Endian.little); } } ///Read double value from binary data(little endian) double readDouble([int index = -1]) { if (_error) { throw DownloadErrorException(_errorMsg); } if (index == -1) { if ((_bufferSize - _index) < 8) { throw NotReadyException(); } var floatData = _underlyingData.buffer.asByteData(_index, 8); _index += 8; return floatData.getFloat64(0, Endian.little); } else { if ((_bufferSize - index) < 8) { throw NotReadyException(); } var floatData = _underlyingData.buffer.asByteData(index, 8); _index = index + 8; return floatData.getFloat64(0, Endian.little); } } ///Read bool value from binary data bool readBool([int index = -1]) { if (_error) { throw DownloadErrorException(_errorMsg); } if (index == -1) { if ((_bufferSize - _index) < 1) { throw NotReadyException(); } var boolData = _underlyingData.buffer.asByteData(_index, 1); _index++; return boolData.getInt8(0) == 1; } else { if ((_bufferSize - index) < 1) { throw NotReadyException(); } var boolData = _underlyingData.buffer.asByteData(index, 1); _index = index + 1; return boolData.getInt8(0) == 1; } } ///Read byte value from binary data int readByte([int index = -1]) { if (_error) { throw DownloadErrorException(_errorMsg); } if (index == -1) { if ((_bufferSize - _index) < 1) { throw NotReadyException(); } var byteData = _underlyingData.buffer.asByteData(_index, 1); _index++; return byteData.getInt8(0); } else { if ((_bufferSize - index) < 1) { throw NotReadyException(); } var byteData = _underlyingData.buffer.asByteData(index, 1); _index = index + 1; return byteData.getInt8(0); } } ///Read bytes from the binary data. Uint8List readBytes([int index = -1]) { if (_error) { throw DownloadErrorException(_errorMsg); } var dataLength = readInt(index); if ((_bufferSize - _index) < dataLength) { throw NotReadyException(); } var bytes = _underlyingData.buffer.asUint8List(_index, dataLength); _index += dataLength; return bytes; } }