parse method

  1. @override
Future<bool> parse(
  1. Socket socket,
  2. Uri uri,
  3. Map<String, String> headers
)
override

Parses the request and returns the data to the socket.

Handles M3U8 playlist and segment requests, replacing URLs with local proxy URLs. Returns true if parsing and response succeed, otherwise false.

Implementation

@override
Future<bool> parse(
  Socket socket,
  Uri uri,
  Map<String, String> headers,
) async {
  try {
    DownloadTask task = DownloadTask(
      uri: uri,
      hlsKey: uri.generateMd5,
      headers: headers,
    );
    HlsSegment? hlsSegment = findSegmentByUri(uri);
    if (hlsSegment != null) {
      task.hlsKey = hlsSegment.key;
    }
    // parse EXT-X-BYTERANGE to get range header
    if (headers.containsKey('range')) {
      String range = headers['range'] ?? '';
      if (range.startsWith("bytes=")) range = range.substring(6);
      List rangeList = range.split('-');
      if (rangeList.length == 2) {
        task.startRange = int.tryParse(rangeList[0]) ?? 0;
        task.endRange = int.tryParse(rangeList[1]);
      }
    }
    Uint8List? data = await cache(task);
    if (data == null) {
      if (VideoProxy.downloadManager.isUrlDownloading(task)) {
        while (data == null) {
          await Future.delayed(const Duration(milliseconds: 100));
          data = await cache(task);
        }
      }
      concurrentLoop(hlsSegment, headers);
      task.priority += 2;
      data = await download(task);
    }
    if (data == null) return false;
    String contentType = 'application/octet-stream';
    if (VideoProxy.urlMatcherImpl.matchM3u8(task.uri)) {
      StringBuffer buffer = modifyM3u8File(task.uri, data, task.hlsKey!);
      data = Uint8List.fromList(buffer.toString().codeUnits);
      contentType = 'application/vnd.apple.mpegurl';
    } else if (VideoProxy.urlMatcherImpl.matchM3u8Key(task.uri)) {
      contentType = 'application/octet-stream';
    } else if (VideoProxy.urlMatcherImpl.matchM3u8Segment(task.uri)) {
      contentType = 'video/MP2T';
    }

    // return contentRange and contentLength to video player which parse from EXT-X-BYTERANGE
    String contentRange = "";
    String contentLength = "";
    if (task.endRange != null) {
      contentRange = 'bytes=${task.startRange}-${task.endRange!}';
      contentLength = (task.endRange! - task.startRange + 1).toString();
    }
    String responseHeaders = <String>[
      contentRange.isEmpty
          ? 'HTTP/1.1 200 OK'
          : 'HTTP/1.1 206 Partial Content',
      'Content-Type: $contentType',
      'Connection: keep-alive',
      if (contentType == 'video/MP2T') 'Accept-Ranges: bytes',
      if (contentRange.isNotEmpty) 'Content-Range: $contentRange',
      if (contentLength.isNotEmpty) 'Content-Length: $contentLength',
    ].join('\r\n');

    await socket.append(responseHeaders);
    await socket.append(data);
    await socket.flush();
    logD('Return request data: $uri');
    return true;
  } catch (e) {
    logW('[UrlParserM3U8] ⚠ ⚠ ⚠ parse socket close: $e');
    return false;
  } finally {
    await socket.close();
    logD('Connection closed\n');
  }
}