fromM3u8PlaylistUrl static method

Future<Map<String, VideoSource>> fromM3u8PlaylistUrl(
  1. String m3u8, {
  2. String initialSubtitle = "",
  3. Map<String, VideoViewerSubtitle>? subtitle,
  4. List<VideoViewerAd>? ads,
  5. Tween<Duration>? range,
  6. String formatter(
    1. String quality
    )?,
  7. bool descending = true,
})

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