parseFromEntryUrl method

Future<HlsParsedResult> parseFromEntryUrl(
  1. String entryUrl
)

Implementation

Future<HlsParsedResult> parseFromEntryUrl(String entryUrl) async {
  final entryUri = Uri.parse(entryUrl);
  final entryText = (await dio.get<String>(entryUrl,
              options: Options(responseType: ResponseType.plain)))
          .data ??
      '';

  final parser = HlsPlaylistParser.create();
  final entryPlaylist = await parser.parseString(entryUri, entryText);

  String mediaUrl = entryUrl;
  if (entryPlaylist is HlsMasterPlaylist) {
    if (entryPlaylist.variants.isEmpty) {
      throw StateError('Master playlist has no variants.');
    }

    // 优先选择“有视频轨且兼容性更高”的流,避免只音频或设备不兼容导致黑屏有声
    final variants = [...entryPlaylist.variants];
    variants.sort((a, b) {
      int scoreOf(Variant v) {
        final format = v.format;
        final codecs = (format.codecs ?? '').toLowerCase();
        final hasResolution = format.width != null || format.height != null;

        int score = 0;
        // 明确标记为 H264 的轨道优先,兼容性最好
        if (codecs.contains('avc1')) score += 1000;
        // 仅有视频轨信息优先
        if (hasResolution) score += 500;
        // HEVC 次优,部分设备或播放器兼容性较弱
        if (codecs.contains('hvc1') || codecs.contains('hev1')) score += 200;

        score += (format.bitrate ?? 0) ~/ 1000;
        return score;
      }

      return scoreOf(b).compareTo(scoreOf(a));
    });

    mediaUrl = variants.first.url as String;
  }
  final mediaUri = Uri.parse(mediaUrl);
  final mediaText = (await dio.get<String>(mediaUrl,
              options: Options(responseType: ResponseType.plain)))
          .data ??
      '';
  final mediaPlaylist = await parser.parseString(mediaUri, mediaText);
  if (mediaPlaylist is! HlsMediaPlaylist) {
    throw StateError('Parsed playlist is not a media playlist.');
  }

  KeyInfo? key;
  String? keyUri;
  String? ivHex;
  for (final seg in mediaPlaylist.segments) {
    if (seg.fullSegmentEncryptionKeyUri != null) {
      keyUri = seg.fullSegmentEncryptionKeyUri!;
      ivHex = seg.encryptionIV;
      break;
    }
  }
  if (keyUri != null) {
    key = KeyInfo()
      ..method = 'AES-128'
      ..uri = _absUrl(keyUri, mediaUri)
      ..localName = 'key.bin'
      ..ivHex = ivHex;
  }

  final segments = <Segment>[];
  for (int i = 0; i < mediaPlaylist.segments.length; i++) {
    final s = mediaPlaylist.segments[i];
    final abs = _absUrl(s.url!, mediaUri);
    segments.add(Segment(
        index: i,
        duration: s.durationUs ?? 0,
        remoteUri: abs,
        localName: 'seg_${(i + 1).toString().padLeft(5, '0')}.ts'));
  }
  int seqBase = 0;
  try {
    final dynamic dyn = mediaPlaylist;
    seqBase = dyn.mediaSequence ?? 0;
  } catch (_) {}
  return HlsParsedResult(
      segments: segments,
      key: key,
      mediaContent: mediaText,
      mediaBase: mediaUri,
      mediaSequenceBase: seqBase);
}