As there are not many articles downloaded from the shutter breakpoint, and there are few cases, I found them on the DIO case after searching for them for a long time, and it took me some time to be wronged
based on DIO: ^ 4.0.0
and Path_ Provider: ^ 2.0.2
implements the function of downloading a large file
Core code
import 'dart:async';
import 'dart:io';
import 'dart:typed_data';
import 'package:dio/dio.dart';
import 'package:path_provider/path_provider.dart';
class DownloadFile {
/// Used to record the url being downloaded to avoid duplicate downloads
static var downloadingUrls = Map<String, CancelToken>();
/// Breakpoint downloading of large files
static Future<void> download({
required String url,
required String savePath,
ProgressCallback?onReceiveProgress,
void Function()?done,
void Function(DioError)?failed,
}) async {
int downloadStart = 0;
bool fileExists = false;
File f = File(savePath);
if (await f.exists()) {
downloadStart = f.lengthSync();
fileExists = true;
}
print("start: $downloadStart");
if (fileExists && downloadingUrls.containsKey(url)) {
return;
}
var dio = Dio();
CancelToken cancelToken = CancelToken();
downloadingUrls[url] = cancelToken;
try {
var response = await dio.get<ResponseBody>(
url,
options: Options(
/// Receive response data as a stream
responseType: ResponseType.stream,
followRedirects: false,
headers: {
/// Downloading key locations in segments
"range": "bytes=$downloadStart-",
},
),
);
File file = File(savePath);
RandomAccessFile raf = file.openSync(mode: FileMode.append);
int received = downloadStart;
int total = await _getContentLength(response);
Stream<Uint8List> stream = response.data!.stream;
StreamSubscription<Uint8List>?subscription;
subscription = stream.listen(
(data) {
/// Write files must be synchronized
raf.writeFromSync(data);
received += data.length;
onReceiveProgress?.call(received, total);
},
onDone: () async {
downloadingUrls.remove(url);
await raf.close();
done?.call();
},
onError: (e) async {
await raf.close();
downloadingUrls.remove(url);
failed?.call(e as DioError);
},
cancelOnError: true,
);
cancelToken.whenCancel.then((_) async {
await subscription?.cancel();
await raf.close();
});
} on DioError catch (error) {
/// The request has been sent and the server responds with a status code that it is not in the range of 200
if (CancelToken.isCancel(error)) {
print("Download cancelled");
} else {
failed?.call(error);
}
downloadingUrls.remove(url);
}
}
/// Get the size of the downloaded file
static Future<int> _getContentLength(Response<ResponseBody> response) async {
try {
var headerContent =
response.headers.value(HttpHeaders.contentRangeHeader);
print("download files: $headerContent");
if (headerContent != null) {
return int.parse(headerContent.split('/').last);
} else {
return 0;
}
} catch (e) {
return 0;
}
}
/// Cancel Mission
static void cancelDownload(String url) {
downloadingUrls[url]?.cancel();
downloadingUrls.remove(url);
}
}
Call case
void main() {
runApp(TestMyApp());
}
class TestMyApp extends StatefulWidget {
@override
State<StatefulWidget> createState() {
return _TestMyAppState();
}
}
class _TestMyAppState extends State<TestMyApp> {
void download() async {
var url = "mp4";
Directory dir = await getApplicationDocumentsDirectory();
var sDCardDir = dir.path;
var savePath = sDCardDir + "/video/1.mp4";
File f = File(sDCardDir + "/video");
if (!await f.exists()) {
new Directory(sDCardDir + "/video").createSync();
}
await DownLoadManage().download(
url: url,
savePath: savePath,
onReceiveProgress: (received, total) {
if (total != -1) {
print("Download1Received:" +
received.toString() +
"Total:" +
total.toString() +
"Progress.+${(received/total * 100).floor()}%");
}
},
done: () {
print("Download 1 completed");
},
failed: (e) {
print("Download 1 failed:" + e.toString());
},
);
}
@override
Widget build(BuildContext context) {
return Container(
color: Colors.white,
child: Center(
child: GestureDetector(
onTap: () {
download();
},
child: Container(
width: 150,
height: 150,
color: Colors.red,
),
),
),
);
}
}