parseIOS method

Future<void> parseIOS(
  1. Socket socket,
  2. Uri uri,
  3. List<String> responseHeaders,
  4. int requestRangeStart,
  5. int requestRangeEnd,
  6. bool partial,
  7. Map<String, String> headers,
)

Parses and responds to range requests on iOS.

Handles segmented download and response for large files.

Implementation

Future<void> parseIOS(
  Socket socket,
  Uri uri,
  List<String> responseHeaders,
  int requestRangeStart,
  int requestRangeEnd,
  bool partial,
  Map<String, String> headers,
) async {
  final totalContentLength = await _contentLength(uri, headers);
  final rangeResponse = Mp4RangeResponse.fromRequest(
    start: requestRangeStart,
    end: requestRangeEnd,
    totalLength: totalContentLength,
    partial: partial,
  );
  responseHeaders.add('content-length: ${rangeResponse.contentLength}');
  if (rangeResponse.contentRangeHeader != null) {
    responseHeaders.add(rangeResponse.contentRangeHeader!);
  }
  await socket.append(responseHeaders.join('\r\n'));

  bool downloading = true;
  int startRange =
      rangeResponse.start - (rangeResponse.start % Config.segmentSize);
  int endRange = startRange + Config.segmentSize - 1;
  int retry = 3;
  while (downloading) {
    // The last segment's endRange must not exceed the file's last byte. When
    // the requested range end is past EOF, some origins (e.g. Aliyun OSS) reply
    // 200 + the whole file instead of a clamped 206; the serve loop then
    // splices the file's head where its tail belongs, corrupting playback
    // around the segment boundary (jump to EOF). rangeResponse.end is the
    // resolved last byte (the request end, or contentLength-1 for `bytes=N-`),
    // so clamp to it. Note requestRangeEnd is still -1 for open-ended requests.
    if (endRange > rangeResponse.end) endRange = rangeResponse.end;
    DownloadTask task = DownloadTask(
      uri: uri,
      startRange: startRange,
      endRange: endRange,
      headers: headers,
    );
    logD(
      'Start ${task.url} '
      'Request range:${task.startRange}-${task.endRange}',
    );

    await concurrent(task, headers, totalContentLength);
    Uint8List? data = await cache(task);
    // if the task has been added, wait for the download to complete
    bool exitUri = VideoProxy.downloadManager.isTaskExit(task);
    if (exitUri) {
      while (data == null) {
        await Future.delayed(const Duration(milliseconds: 100));
        data = await cache(task);
      }
    }
    if (data == null) {
      task.priority += 2;
      data = await download(task);
    }
    if (data == null) {
      retry--;
      if (retry == 0) {
        downloading = false;
        break;
      }
      continue;
    }

    int startIndex = 0;
    int? endIndex;
    if (startRange < rangeResponse.start) {
      startIndex = rangeResponse.start - startRange;
    }
    if (endRange > rangeResponse.end) {
      endIndex = rangeResponse.end - startRange + 1;
    }
    data = data.sublist(startIndex, endIndex);
    socket.done.then((value) {
      downloading = false;
    }).catchError((e) {
      downloading = false;
    });
    bool success = await socket.append(data);
    if (!success) downloading = false;
    startRange += Config.segmentSize;
    endRange = startRange + Config.segmentSize - 1;
    if (startRange > rangeResponse.end) {
      downloading = false;
    }
  }
}