modifyM3u8File method

StringBuffer modifyM3u8File(
  1. Uri uri,
  2. Uint8List data,
  3. String hlsKey
)

Parse and rewrite M3U8 playlist lines for local proxying

Implementation

StringBuffer modifyM3u8File(Uri uri, Uint8List data, String hlsKey) {
  List<String> lines = readLineFromUint8List(data);
  String lastLine = '';
  int lastEndRange = 0;
  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") ||
        hlsLine.startsWith("#EXT-X-MAP")) {
      Match? match = RegExp(r'URI="([^"]+)"').firstMatch(hlsLine);
      if (match != null && match.groupCount >= 1) {
        parseUri = match.group(1);
        if (parseUri != null) {
          parseUri = parseUri.toSafeUrl();
          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") ||
        hlsLine.startsWith("#EXT-X-MAP")) {
      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: hlsKey));
      }
    }
    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;
          // when hlsLine is relative path
          while (hlsLine.startsWith("../")) {
            hlsLine = hlsLine.substring(3);
            relativePath++;
          }
          // when hlsLine start with /, and prefix contain hlsLine
          String prefix = '${uri.pathPrefix(relativePath)}/';
          if (hlsLine.startsWith("/")) {
            List<String> split = hlsLine.split("/");
            List<String> result = [];
            for (var item in split) {
              if (prefix.contains(item)) continue;
              result.add(item);
            }
            hlsLine = result.join("/");
          }
          hlsLine = prefix + hlsLine;
        }
        // parse EXT-X-BYTERANGE to get range header
        int startRange = 0;
        int? endRange;
        if (lastLine.startsWith("#EXT-X-BYTERANGE")) {
          final reg = RegExp(r'#EXT-X-BYTERANGE:(\d+)(?:@(\d+))?');
          final match = reg.firstMatch(line);
          if (match != null) {
            if (match.groupCount == 2) {
              int offset = int.tryParse(match.group(1)!) ?? 0;
              startRange = int.tryParse(match.group(2)!) ?? 0;
              endRange = offset == 0 ? null : startRange + offset;
            } else if (match.groupCount == 1) {
              startRange = lastEndRange;
              int offset = int.tryParse(match.group(1)!) ?? 0;
              endRange = offset == 0 ? null : startRange + offset;
              lastEndRange = endRange ?? 0;
            }
          }
        }
        concurrentAdd(HlsSegment(
          url: hlsLine,
          key: hlsKey,
          startRange: startRange,
          endRange: endRange,
        ));
      }
    }
    buffer.write('$line\r\n');
    lastLine = line;
  }
  return buffer;
}