modifyM3u8File method
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;
}