parse method
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');
}
}