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;
    Uint8List? data = await cache(task);
    if (data == null) {
      concurrentLoop(hlsSegment, headers);
      task.priority += 10;
      data = await download(task);
    }
    if (data == null) return false;
    String contentType = 'application/octet-stream';
    if (VideoProxy.urlMatcherImpl.matchM3u8(task.uri)) {
      // Parse and rewrite M3U8 playlist lines for local proxying
      List<String> lines = readLineFromUint8List(data);
      String lastLine = '';
      StringBuffer buffer = StringBuffer();
      for (String line in lines) {
        String hlsLine = line.trim();
        String? parseUri;
        if (hlsLine.startsWith("#EXT-X-KEY") ||
            hlsLine.startsWith("#EXT-X-MEDIA")) {
          Match? match = RegExp(r'URI="([^"]+)"').firstMatch(hlsLine);
          if (match != null && match.groupCount >= 1) {
            parseUri = match.group(1);
            if (parseUri != null) {
              String newUri = parseUri.startsWith('http')
                  ? parseUri.toLocalUrl()
                  : '$parseUri${parseUri.contains('?') ? '&' : '?'}'
                      'origin=${base64Url.encode(utf8.encode(uri.origin))}';
              line = hlsLine.replaceAll(parseUri, newUri);
            }
          }
        }
        if (lastLine.startsWith("#EXTINF") ||
            lastLine.startsWith("#EXT-X-BYTERANGE") ||
            lastLine.startsWith("#EXT-X-STREAM-INF")) {
          if (!line.startsWith("#EXT")) {
            line = line.toSafeUrl();
            line = line.startsWith('http')
                ? line.toLocalUrl()
                : '$line${line.contains('?') ? '&' : '?'}'
                    'origin=${base64Url.encode(utf8.encode(uri.origin))}';
          }
        }
        // Add HLS segment to download list
        if (hlsLine.startsWith("#EXT-X-KEY") ||
            hlsLine.startsWith("#EXT-X-MEDIA")) {
          if (parseUri != null) {
            if (!parseUri.startsWith('http')) {
              int relativePath = 0;
              while (hlsLine.startsWith("../")) {
                hlsLine = hlsLine.substring(3);
                relativePath++;
              }
              parseUri = '${uri.pathPrefix(relativePath)}/' + parseUri;
            }
            concurrentAdd(
              HlsSegment(url: parseUri, key: task.hlsKey!),
              headers,
            );
          }
        }
        if (lastLine.startsWith("#EXTINF") ||
            lastLine.startsWith("#EXT-X-BYTERANGE") ||
            lastLine.startsWith("#EXT-X-STREAM-INF")) {
          if (!line.startsWith("#EXT")) {
            if (!hlsLine.startsWith('http')) {
              int relativePath = 0;
              while (hlsLine.startsWith("../")) {
                hlsLine = hlsLine.substring(3);
                relativePath++;
              }
              hlsLine = '${uri.pathPrefix(relativePath)}/' + hlsLine;
            }
            concurrentAdd(
              HlsSegment(url: hlsLine, key: task.hlsKey!),
              headers,
            );
          }
        }
        buffer.write('$line\r\n');
        lastLine = line;
      }
      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';
    }
    String responseHeaders = <String>[
      'HTTP/1.1 200 OK',
      'Content-Type: $contentType',
      'Connection: keep-alive',
      if (contentType == 'video/MP2T') 'Accept-Ranges: bytes',
    ].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');
  }
}