fromM3u8PlaylistUrl static method
It's a function that returns a map from VideoPlayerController.network, the input data must be of type URL.
EXAMPLE:
VideoSource.fromM3u8VideoUrl(
"https://sfux-ext.sfux.info/hls/chapter/105/1588724110/1588724110.m3u8",
formatter: (quality) => quality == "Auto" ? "Automatic" : "${quality.split("x").last}p",
)
Implementation
static Future<Map<String, VideoSource>> fromM3u8PlaylistUrl(
String m3u8, {
String initialSubtitle = "",
Map<String, VideoViewerSubtitle>? subtitle,
List<VideoViewerAd>? ads,
Tween<Duration>? range,
String Function(String quality)? formatter,
bool descending = true,
}) async {
//REGULAR EXPRESIONS//
final RegExp netRegxUrl = RegExp(r'^(http|https):\/\/([\w.]+\/?)\S*');
final RegExp netRegx2 = RegExp(r'(.*)\r?\/');
final RegExp regExpPlaylist = RegExp(
r"#EXT-X-STREAM-INF:(?:.*,RESOLUTION=(\d+x\d+))?,?(.*)\r?\n(.*)",
caseSensitive: false,
multiLine: true,
);
final RegExp regExpAudio = RegExp(
r"""^#EXT-X-MEDIA:TYPE=AUDIO(?:.*,URI="(.*m3u8)")""",
caseSensitive: false,
multiLine: true,
);
//GET m3u8 file
late String content = "";
final http.Response response = await http.get(Uri.parse(m3u8));
if (response.statusCode == 200) content = utf8.decode(response.bodyBytes);
final String directoryPath = (await getTemporaryDirectory()).path;
//Find matches
List<RegExpMatch> playlistMatches =
regExpPlaylist.allMatches(content).toList();
List<RegExpMatch> audioMatches = regExpAudio.allMatches(content).toList();
Map<String, File> sources = {};
final List<String> audioUrls = [];
for (final RegExpMatch playlistMatch in playlistMatches) {
final RegExpMatch? playlist = netRegx2.firstMatch(m3u8);
final String sourceURL = (playlistMatch.group(3)).toString();
final String quality = (playlistMatch.group(1)).toString();
final bool isNetwork = netRegxUrl.hasMatch(sourceURL);
String playlistUrl = sourceURL;
if (!isNetwork) {
final String? dataURL = playlist!.group(0);
playlistUrl = "$dataURL$sourceURL";
}
//Find audio url
for (final RegExpMatch audioMatch in audioMatches) {
final String audio = (audioMatch.group(1)).toString();
final bool isNetwork = netRegxUrl.hasMatch(audio);
final RegExpMatch? match = netRegx2.firstMatch(playlistUrl);
String audioUrl = audio;
if (!isNetwork && match != null) {
audioUrl = "${match.group(0)}$audio";
}
audioUrls.add(audioUrl);
}
final String audioMetadata;
if (audioUrls.length > 0) {
audioMetadata =
"""#EXT-X-MEDIA:TYPE=AUDIO,GROUP-ID="audio-medium",NAME="audio",AUTOSELECT=YES,DEFAULT=YES,CHANNELS="2",URI="${audioUrls.last}"\n""";
} else {
audioMetadata = "";
}
final File file = File('$directoryPath/hls$quality.m3u8');
file.writeAsStringSync(
"""#EXTM3U\n#EXT-X-INDEPENDENT-SEGMENTS\n$audioMetadata#EXT-X-STREAM-INF:CLOSED-CAPTIONS=NONE,BANDWIDTH=1469712,RESOLUTION=$quality,FRAME-RATE=30.000\n$playlistUrl""",
);
sources[quality] = file;
}
Map<String, VideoSource> videoSource = {};
void addAutoSource() {
videoSource["Auto"] = VideoSource(
video: VideoPlayerController.network(m3u8),
intialSubtitle: initialSubtitle,
subtitle: subtitle,
range: range,
ads: ads,
);
}
if (descending) addAutoSource();
for (final entry
in descending ? sources.entries.toList().reversed : sources.entries) {
final String key = formatter?.call(entry.key) ?? entry.key;
videoSource[key] = VideoSource(
video: VideoPlayerController.file(sources[entry.key]!),
intialSubtitle: initialSubtitle,
subtitle: subtitle,
range: range,
ads: ads,
);
}
if (!descending) addAutoSource();
return videoSource;
}