content: /*
   * Copyright (C) 2016 The Android Open Source Project
   *
   * Licensed under the Apache License, Version 2.0 (the "License");
   * you may not use this file except in compliance with the License.
   * You may obtain a copy of the License at
   *
   *      http://www.apache.org/licenses/LICENSE-2.0
   *
   * Unless required by applicable law or agreed to in writing, software
   * distributed under the License is distributed on an "AS IS" BASIS,
   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
   * See the License for the specific language governing permissions and
   * limitations under the License.
   */
  package com.google.android.exoplayer2.source.hls.playlist;
  
  import static com.google.common.truth.Truth.assertThat;
  import static org.junit.Assert.fail;
  
  import android.net.Uri;
  import androidx.test.ext.junit.runners.AndroidJUnit4;
  import com.google.android.exoplayer2.C;
  import com.google.android.exoplayer2.Format;
  import com.google.android.exoplayer2.ParserException;
  import com.google.android.exoplayer2.metadata.Metadata;
  import com.google.android.exoplayer2.source.hls.HlsTrackMetadataEntry;
  import com.google.android.exoplayer2.source.hls.playlist.HlsMultivariantPlaylist.Variant;
  import com.google.android.exoplayer2.util.MimeTypes;
  import com.google.common.base.Charsets;
  import java.io.ByteArrayInputStream;
  import java.io.IOException;
  import java.util.Arrays;
  import java.util.Collections;
  import java.util.List;
  import org.junit.Test;
  import org.junit.runner.RunWith;
  
  /** Test for {@link HlsMultivariantPlaylist}. */
  @RunWith(AndroidJUnit4.class)
  public class HlsMultivariantPlaylistParserTest {
  
    private static final String PLAYLIST_URI = "https://example.com/test.m3u8";
  
    private static final String PLAYLIST_SIMPLE =
        " #EXTM3U \n"
            + "\n"
            + "#EXT-X-STREAM-INF:BANDWIDTH=1280000,"
            + "CODECS=\"mp4a.40.2,avc1.66.30\",RESOLUTION=304x128\n"
            + "http://example.com/low.m3u8\n"
            + "\n"
            + "#EXT-X-STREAM-INF:BANDWIDTH=1280000,CODECS=\"mp4a.40.2 , avc1.66.30 \"\n"
            + "http://example.com/spaces_in_codecs.m3u8\n"
            + "\n"
            + "#EXT-X-STREAM-INF:BANDWIDTH=2560000,FRAME-RATE=25,RESOLUTION=384x160\n"
            + "http://example.com/mid.m3u8\n"
            + "\n"
            + "#EXT-X-STREAM-INF:BANDWIDTH=7680000,FRAME-RATE=29.997\n"
            + "http://example.com/hi.m3u8\n"
            + "\n"
            + "#EXT-X-STREAM-INF:BANDWIDTH=65000,CODECS=\"mp4a.40.5\"\n"
            + "http://example.com/audio-only.m3u8";
  
    private static final String PLAYLIST_WITH_AVG_BANDWIDTH =
        " #EXTM3U \n"
            + "\n"
            + "#EXT-X-STREAM-INF:BANDWIDTH=1280000,"
            + "CODECS=\"mp4a.40.2,avc1.66.30\",RESOLUTION=304x128\n"
            + "http://example.com/low.m3u8\n"
            + "\n"
            + "#EXT-X-STREAM-INF:BANDWIDTH=1280000,AVERAGE-BANDWIDTH=1270000,"
            + "CODECS=\"mp4a.40.2 , avc1.66.30 \"\n"
            + "http://example.com/spaces_in_codecs.m3u8\n";
  
    private static final String PLAYLIST_WITH_INVALID_HEADER =
        "#EXTMU3\n"
            + "#EXT-X-STREAM-INF:BANDWIDTH=1280000,"
            + "CODECS=\"mp4a.40.2,avc1.66.30\",RESOLUTION=304x128\n"
            + "http://example.com/low.m3u8\n";
  
    private static final String PLAYLIST_WITH_CC =
        " #EXTM3U \n"
            + "#EXT-X-MEDIA:TYPE=CLOSED-CAPTIONS,GROUP-ID=\"cc1\","
            + "LANGUAGE=\"es\",NAME=\"Eng\",INSTREAM-ID=\"SERVICE4\"\n"
            + "#EXT-X-STREAM-INF:BANDWIDTH=1280000,"
            + "CODECS=\"mp4a.40.2,avc1.66.30\",RESOLUTION=304x128\n"
            + "http://example.com/low.m3u8\n";
  
    private static final String PLAYLIST_WITH_CHANNELS_ATTRIBUTE =
        " #EXTM3U \n"
            + "#EXT-X-MEDIA:TYPE=AUDIO,GROUP-ID=\"audio\",CHANNELS=\"6\",NAME=\"Eng6\","
            + "URI=\"something.m3u8\"\n"
            + "#EXT-X-MEDIA:TYPE=AUDIO,GROUP-ID=\"audio\",CHANNELS=\"2/6\",NAME=\"Eng26\","
            + "URI=\"something2.m3u8\"\n"
            + "#EXT-X-MEDIA:TYPE=AUDIO,GROUP-ID=\"audio\",NAME=\"Eng\","
            + "URI=\"something3.m3u8\"\n"
            + "#EXT-X-STREAM-INF:BANDWIDTH=1280000,"
            + "CODECS=\"mp4a.40.2,avc1.66.30\",AUDIO=\"audio\",RESOLUTION=304x128\n"
            + "http://example.com/low.m3u8\n";
  
    private static final String PLAYLIST_WITHOUT_CC =
        " #EXTM3U \n"
            + "#EXT-X-MEDIA:TYPE=CLOSED-CAPTIONS,GROUP-ID=\"cc1\","
            + "LANGUAGE=\"es\",NAME=\"Eng\",INSTREAM-ID=\"SERVICE4\"\n"
            + "#EXT-X-STREAM-INF:BANDWIDTH=1280000,"
            + "CODECS=\"mp4a.40.2,avc1.66.30\",RESOLUTION=304x128,"
            + "CLOSED-CAPTIONS=NONE\n"
            + "http://example.com/low.m3u8\n";
  
    private static final String PLAYLIST_WITH_SUBTITLES =
        " #EXTM3U \n"
            + "#EXT-X-MEDIA:TYPE=SUBTITLES,GROUP-ID=\"sub1\",URI=\"s1/en/prog_index.m3u8\","
            + "LANGUAGE=\"es\",NAME=\"Eng\"\n"
            + "#EXT-X-STREAM-INF:BANDWIDTH=1280000,"
            + "CODECS=\"mp4a.40.2,avc1.66.30\",RESOLUTION=304x128\n"
            + "http://example.com/low.m3u8\n";
  
    private static final String PLAYLIST_WITH_SUBTITLES_NO_URI =
        " #EXTM3U \n"
            + "#EXT-X-MEDIA:TYPE=SUBTITLES,GROUP-ID=\"sub1\","
            + "LANGUAGE=\"es\",NAME=\"Eng\"\n"
            + "#EXT-X-STREAM-INF:BANDWIDTH=1280000,"
            + "CODECS=\"mp4a.40.2,avc1.66.30\",RESOLUTION=304x128\n"
            + "http://example.com/low.m3u8\n";
  
    private static final String PLAYLIST_WITH_AUDIO_MEDIA_TAG =
        "#EXTM3U\n"
            + "#EXT-X-STREAM-INF:BANDWIDTH=2227464,CODECS=\"avc1.640020,mp4a.40.2\",AUDIO=\"aud1\"\n"
            + "uri1.m3u8\n"
            + "#EXT-X-STREAM-INF:BANDWIDTH=8178040,CODECS=\"avc1.64002a,mp4a.40.2\",AUDIO=\"aud1\"\n"
            + "uri2.m3u8\n"
            + "#EXT-X-STREAM-INF:BANDWIDTH=2448841,CODECS=\"avc1.640020,ac-3\",AUDIO=\"aud2\"\n"
            + "uri1.m3u8\n"
            + "#EXT-X-STREAM-INF:BANDWIDTH=8399417,CODECS=\"avc1.64002a,ac-3\",AUDIO=\"aud2\"\n"
            + "uri2.m3u8\n"
            + "#EXT-X-MEDIA:TYPE=AUDIO,GROUP-ID=\"aud1\",LANGUAGE=\"en\",NAME=\"English\","
            + "AUTOSELECT=YES,DEFAULT=YES,CHANNELS=\"2\",URI=\"a1/prog_index.m3u8\"\n"
            + "#EXT-X-MEDIA:TYPE=AUDIO,GROUP-ID=\"aud2\",LANGUAGE=\"en\",NAME=\"English\","
            + "AUTOSELECT=YES,DEFAULT=YES,CHANNELS=\"6\",URI=\"a2/prog_index.m3u8\"\n";
  
    private static final String PLAYLIST_WITH_INDEPENDENT_SEGMENTS =
        " #EXTM3U\n"
            + "\n"
            + "#EXT-X-INDEPENDENT-SEGMENTS\n"
            + "\n"
            + "#EXT-X-STREAM-INF:BANDWIDTH=1280000,"
            + "CODECS=\"mp4a.40.2,avc1.66.30\",RESOLUTION=304x128\n"
            + "http://example.com/low.m3u8\n"
            + "\n"
            + "#EXT-X-STREAM-INF:BANDWIDTH=1280000,CODECS=\"mp4a.40.2 , avc1.66.30 \"\n"
            + "http://example.com/spaces_in_codecs.m3u8\n";
  
    private static final String PLAYLIST_WITH_VARIABLE_SUBSTITUTION =
        " #EXTM3U \n"
            + "\n"
            + "#EXT-X-DEFINE:NAME=\"codecs\",VALUE=\"mp4a.40.5\"\n"
            + "#EXT-X-DEFINE:NAME=\"tricky\",VALUE=\"This/{$nested}/reference/shouldnt/work\"\n"
            + "#EXT-X-DEFINE:NAME=\"nested\",VALUE=\"This should not be inserted\"\n"
            + "#EXT-X-STREAM-INF:BANDWIDTH=65000,CODECS=\"{$codecs}\"\n"
            + "http://example.com/{$tricky}\n";
  
    private static final String PLAYLIST_WITH_MATCHING_STREAM_INF_URLS =
        "#EXTM3U\n"
            + "#EXT-X-VERSION:6\n"
            + "\n"
            + "#EXT-X-STREAM-INF:BANDWIDTH=2227464,"
            + "CLOSED-CAPTIONS=\"cc1\",AUDIO=\"aud1\",SUBTITLES=\"sub1\"\n"
            + "v5/prog_index.m3u8\n"
            + "#EXT-X-STREAM-INF:BANDWIDTH=6453202,"
            + "CLOSED-CAPTIONS=\"cc1\",AUDIO=\"aud1\",SUBTITLES=\"sub1\"\n"
            + "v8/prog_index.m3u8\n"
            + "#EXT-X-STREAM-INF:BANDWIDTH=5054232,"
            + "CLOSED-CAPTIONS=\"cc1\",AUDIO=\"aud1\",SUBTITLES=\"sub1\"\n"
            + "v7/prog_index.m3u8\n"
            + "\n"
            + "#EXT-X-STREAM-INF:BANDWIDTH=2448841,"
            + "CLOSED-CAPTIONS=\"cc1\",AUDIO=\"aud2\",SUBTITLES=\"sub1\"\n"
            + "v5/prog_index.m3u8\n"
            + "#EXT-X-STREAM-INF:BANDWIDTH=8399417,"
            + "CLOSED-CAPTIONS=\"cc1\",AUDIO=\"aud2\",SUBTITLES=\"sub1\"\n"
            + "v9/prog_index.m3u8\n"
            + "#EXT-X-STREAM-INF:BANDWIDTH=5275609,"
            + "CLOSED-CAPTIONS=\"cc1\",AUDIO=\"aud2\",SUBTITLES=\"sub1\"\n"
            + "v7/prog_index.m3u8\n"
            + "\n"
            + "#EXT-X-STREAM-INF:BANDWIDTH=2256841,"
            + "CLOSED-CAPTIONS=\"cc1\",AUDIO=\"aud3\",SUBTITLES=\"sub1\"\n"
            + "v5/prog_index.m3u8\n"
            + "#EXT-X-STREAM-INF:BANDWIDTH=8207417,"
            + "CLOSED-CAPTIONS=\"cc1\",AUDIO=\"aud3\",SUBTITLES=\"sub1\"\n"
            + "v9/prog_index.m3u8\n"
            + "#EXT-X-STREAM-INF:BANDWIDTH=6482579,"
            + "CLOSED-CAPTIONS=\"cc1\",AUDIO=\"aud3\",SUBTITLES=\"sub1\"\n"
            + "v8/prog_index.m3u8\n"
            + "\n"
            + "#EXT-X-MEDIA:TYPE=AUDIO,GROUP-ID=\"aud1\",NAME=\"English\",URI=\"a1/index.m3u8\"\n"
            + "#EXT-X-MEDIA:TYPE=AUDIO,GROUP-ID=\"aud2\",NAME=\"English\",URI=\"a2/index.m3u8\"\n"
            + "#EXT-X-MEDIA:TYPE=AUDIO,GROUP-ID=\"aud3\",NAME=\"English\",URI=\"a3/index.m3u8\"\n"
            + "\n"
            + "#EXT-X-MEDIA:TYPE=CLOSED-CAPTIONS,"
            + "GROUP-ID=\"cc1\",NAME=\"English\",INSTREAM-ID=\"CC1\"\n"
            + "\n"
            + "#EXT-X-MEDIA:TYPE=SUBTITLES,"
            + "GROUP-ID=\"sub1\",NAME=\"English\",URI=\"s1/en/prog_index.m3u8\"\n";
  
    private static final String PLAYLIST_WITH_TTML_SUBTITLE =
        " #EXTM3U\n"
            + "\n"
            + "#EXT-X-VERSION:6\n"
            + "\n"
            + "#EXT-X-INDEPENDENT-SEGMENTS\n"
            + "\n"
            + "#EXT-X-STREAM-INF:BANDWIDTH=1280000,CODECS=\"stpp.ttml.im1t,mp4a.40.2,avc1.66.30\",RESOLUTION=304x128,AUDIO=\"aud1\",SUBTITLES=\"sub1\"\n"
            + "http://example.com/low.m3u8\n"
            + "\n"
            + "#EXT-X-MEDIA:TYPE=AUDIO,GROUP-ID=\"aud1\",NAME=\"English\",URI=\"a1/index.m3u8\"\n"
            + "#EXT-X-MEDIA:TYPE=SUBTITLES,GROUP-ID=\"sub1\",NAME=\"English\",AUTOSELECT=YES,DEFAULT=YES,URI=\"s1/en/prog_index.m3u8\"\n";
  
    private static final String PLAYLIST_WITH_IFRAME_VARIANTS =
        "#EXTM3U\n"
            + "#EXT-X-VERSION:5\n"
            + "#EXT-X-MEDIA:URI=\"AUDIO_English/index.m3u8\",TYPE=AUDIO,GROUP-ID=\"audio-aac\",LANGUAGE=\"en\",NAME=\"English\",AUTOSELECT=YES\n"
            + "#EXT-X-MEDIA:URI=\"AUDIO_Spanish/index.m3u8\",TYPE=AUDIO,GROUP-ID=\"audio-aac\",LANGUAGE=\"es\",NAME=\"Spanish\",AUTOSELECT=YES\n"
            + "#EXT-X-MEDIA:TYPE=CLOSED-CAPTIONS,GROUP-ID=\"cc1\",LANGUAGE=\"en\",NAME=\"English\",AUTOSELECT=YES,DEFAULT=YES,INSTREAM-ID=\"CC1\"\n"
            + "#EXT-X-STREAM-INF:PROGRAM-ID=1,BANDWIDTH=400000,RESOLUTION=480x320,CODECS=\"mp4a.40.2,avc1.640015\",AUDIO=\"audio-aac\",CLOSED-CAPTIONS=\"cc1\"\n"
            + "400000/index.m3u8\n"
            + "#EXT-X-STREAM-INF:PROGRAM-ID=1,BANDWIDTH=1000000,RESOLUTION=848x480,CODECS=\"mp4a.40.2,avc1.64001f\",AUDIO=\"audio-aac\",CLOSED-CAPTIONS=\"cc1\"\n"
            + "1000000/index.m3u8\n"
            + "#EXT-X-STREAM-INF:PROGRAM-ID=1,BANDWIDTH=3220000,RESOLUTION=1280x720,CODECS=\"mp4a.40.2,avc1.64001f\",AUDIO=\"audio-aac\",CLOSED-CAPTIONS=\"cc1\"\n"
            + "3220000/index.m3u8\n"
            + "#EXT-X-STREAM-INF:PROGRAM-ID=1,BANDWIDTH=8940000,RESOLUTION=1920x1080,CODECS=\"mp4a.40.2,avc1.640028\",AUDIO=\"audio-aac\",CLOSED-CAPTIONS=\"cc1\"\n"
            + "8940000/index.m3u8\n"
            + "#EXT-X-I-FRAME-STREAM-INF:BANDWIDTH=1313400,RESOLUTION=1920x1080,CODECS=\"avc1.640028\",URI=\"iframe_1313400/index.m3u8\"\n";
  
    @Test
    public void parseMultivariantPlaylist_withSimple_success() throws IOException {
      HlsMultivariantPlaylist multivariantPlaylist =
          parseMultivariantPlaylist(PLAYLIST_URI, PLAYLIST_SIMPLE);
  
      List<HlsMultivariantPlaylist.Variant> variants = multivariantPlaylist.variants;
      assertThat(variants).hasSize(5);
      assertThat(multivariantPlaylist.muxedCaptionFormats).isNull();
  
      assertThat(variants.get(0).format.bitrate).isEqualTo(1280000);
      assertThat(variants.get(0).format.codecs).isEqualTo("mp4a.40.2,avc1.66.30");
      assertThat(variants.get(0).format.width).isEqualTo(304);
      assertThat(variants.get(0).format.height).isEqualTo(128);
      assertThat(variants.get(0).url).isEqualTo(Uri.parse("http://example.com/low.m3u8"));
  
      assertThat(variants.get(1).format.bitrate).isEqualTo(1280000);
      assertThat(variants.get(1).format.codecs).isEqualTo("mp4a.40.2 , avc1.66.30 ");
      assertThat(variants.get(1).url)
          .isEqualTo(Uri.parse("http://example.com/spaces_in_codecs.m3u8"));
  
      assertThat(variants.get(2).format.bitrate).isEqualTo(2560000);
      assertThat(variants.get(2).format.codecs).isNull();
      assertThat(variants.get(2).format.width).isEqualTo(384);
      assertThat(variants.get(2).format.height).isEqualTo(160);
      assertThat(variants.get(2).format.frameRate).isEqualTo(25.0f);
      assertThat(variants.get(2).url).isEqualTo(Uri.parse("http://example.com/mid.m3u8"));
  
      assertThat(variants.get(3).format.bitrate).isEqualTo(7680000);
      assertThat(variants.get(3).format.codecs).isNull();
      assertThat(variants.get(3).format.width).isEqualTo(Format.NO_VALUE);
      assertThat(variants.get(3).format.height).isEqualTo(Format.NO_VALUE);
      assertThat(variants.get(3).format.frameRate).isEqualTo(29.997f);
      assertThat(variants.get(3).url).isEqualTo(Uri.parse("http://example.com/hi.m3u8"));
  
      assertThat(variants.get(4).format.bitrate).isEqualTo(65000);
      assertThat(variants.get(4).format.codecs).isEqualTo("mp4a.40.5");
      assertThat(variants.get(4).format.width).isEqualTo(Format.NO_VALUE);
      assertThat(variants.get(4).format.height).isEqualTo(Format.NO_VALUE);
      assertThat(variants.get(4).format.frameRate).isEqualTo((float) Format.NO_VALUE);
      assertThat(variants.get(4).url).isEqualTo(Uri.parse("http://example.com/audio-only.m3u8"));
    }
  
    @Test
    public void parseMultivariantPlaylist_withAverageBandwidth_success() throws IOException {
      HlsMultivariantPlaylist multivariantPlaylist =
          parseMultivariantPlaylist(PLAYLIST_URI, PLAYLIST_WITH_AVG_BANDWIDTH);
  
      List<HlsMultivariantPlaylist.Variant> variants = multivariantPlaylist.variants;
  
      assertThat(variants.get(0).format.bitrate).isEqualTo(1280000);
      assertThat(variants.get(1).format.bitrate).isEqualTo(1280000);
    }
  
    @Test
    public void parseMultivariantPlaylist_withInvalidHeader_throwsException() throws IOException {
      try {
        parseMultivariantPlaylist(PLAYLIST_URI, PLAYLIST_WITH_INVALID_HEADER);
        fail("Expected exception not thrown.");
      } catch (ParserException e) {
        // Expected due to invalid header.
      }
    }
  
    @Test
    public void parseMultivariantPlaylist_withClosedCaption_success() throws IOException {
      HlsMultivariantPlaylist playlist = parseMultivariantPlaylist(PLAYLIST_URI, PLAYLIST_WITH_CC);
      assertThat(playlist.muxedCaptionFormats).hasSize(1);
      Format closedCaptionFormat = playlist.muxedCaptionFormats.get(0);
      assertThat(closedCaptionFormat.sampleMimeType).isEqualTo(MimeTypes.APPLICATION_CEA708);
      assertThat(closedCaptionFormat.accessibilityChannel).isEqualTo(4);
      assertThat(closedCaptionFormat.language).isEqualTo("es");
    }
  
    @Test
    public void parseMultivariantPlaylist_withChannelsAttribute_success() throws IOException {
      HlsMultivariantPlaylist playlist =
          parseMultivariantPlaylist(PLAYLIST_URI, PLAYLIST_WITH_CHANNELS_ATTRIBUTE);
      List<HlsMultivariantPlaylist.Rendition> audios = playlist.audios;
      assertThat(audios).hasSize(3);
      assertThat(audios.get(0).format.channelCount).isEqualTo(6);
      assertThat(audios.get(1).format.channelCount).isEqualTo(2);
      assertThat(audios.get(2).format.channelCount).isEqualTo(Format.NO_VALUE);
    }
  
    @Test
    public void parseMultivariantPlaylist_withoutClosedCaption_success() throws IOException {
      HlsMultivariantPlaylist playlist = parseMultivariantPlaylist(PLAYLIST_URI, PLAYLIST_WITHOUT_CC);
      assertThat(playlist.muxedCaptionFormats).isEmpty();
    }
  
    @Test
    public void parseMultivariantPlaylist_withAudio_codecPropagated() throws IOException {
      HlsMultivariantPlaylist playlist =
          parseMultivariantPlaylist(PLAYLIST_URI, PLAYLIST_WITH_AUDIO_MEDIA_TAG);
  
      Format firstAudioFormat = playlist.audios.get(0).format;
      assertThat(firstAudioFormat.codecs).isEqualTo("mp4a.40.2");
      assertThat(firstAudioFormat.sampleMimeType).isEqualTo(MimeTypes.AUDIO_AAC);
  
      Format secondAudioFormat = playlist.audios.get(1).format;
      assertThat(secondAudioFormat.codecs).isEqualTo("ac-3");
      assertThat(secondAudioFormat.sampleMimeType).isEqualTo(MimeTypes.AUDIO_AC3);
    }
  
    @Test
    public void parseMultivariantPlaylist_withAudio_audioIdPropagated() throws IOException {
      HlsMultivariantPlaylist playlist =
          parseMultivariantPlaylist(PLAYLIST_URI, PLAYLIST_WITH_AUDIO_MEDIA_TAG);
  
      Format firstAudioFormat = playlist.audios.get(0).format;
      assertThat(firstAudioFormat.id).isEqualTo("aud1:English");
  
      Format secondAudioFormat = playlist.audios.get(1).format;
      assertThat(secondAudioFormat.id).isEqualTo("aud2:English");
    }
  
    @Test
    public void parseMultivariantPlaylist_withCc_cCIdPropagated() throws IOException {
      HlsMultivariantPlaylist playlist = parseMultivariantPlaylist(PLAYLIST_URI, PLAYLIST_WITH_CC);
  
      Format firstTextFormat = playlist.muxedCaptionFormats.get(0);
      assertThat(firstTextFormat.id).isEqualTo("cc1:Eng");
    }
  
    @Test
    public void parseMultivariantPlaylist_withSubtitles_subtitlesIdPropagated() throws IOException {
      HlsMultivariantPlaylist playlist =
          parseMultivariantPlaylist(PLAYLIST_URI, PLAYLIST_WITH_SUBTITLES);
  
      Format firstTextFormat = playlist.subtitles.get(0).format;
      assertThat(firstTextFormat.id).isEqualTo("sub1:Eng");
      assertThat(firstTextFormat.sampleMimeType).isEqualTo(MimeTypes.TEXT_VTT);
    }
  
    @Test
    public void parseMultivariantPlaylist_subtitlesWithoutUri_skipsSubtitles() throws IOException {
      HlsMultivariantPlaylist playlist =
          parseMultivariantPlaylist(PLAYLIST_URI, PLAYLIST_WITH_SUBTITLES_NO_URI);
  
      assertThat(playlist.subtitles).isEmpty();
    }
  
    @Test
    public void parseMultivariantPlaylist_withIndependentSegments_hasNoIndenpendentSegments()
        throws IOException {
      HlsMultivariantPlaylist playlistWithIndependentSegments =
          parseMultivariantPlaylist(PLAYLIST_URI, PLAYLIST_WITH_INDEPENDENT_SEGMENTS);
      assertThat(playlistWithIndependentSegments.hasIndependentSegments).isTrue();
  
      HlsMultivariantPlaylist playlistWithoutIndependentSegments =
          parseMultivariantPlaylist(PLAYLIST_URI, PLAYLIST_SIMPLE);
      assertThat(playlistWithoutIndependentSegments.hasIndependentSegments).isFalse();
    }
  
    @Test
    public void parseMultivariantPlaylist_withVariableSubstitution_success() throws IOException {
      HlsMultivariantPlaylist playlistWithSubstitutions =
          parseMultivariantPlaylist(PLAYLIST_URI, PLAYLIST_WITH_VARIABLE_SUBSTITUTION);
      HlsMultivariantPlaylist.Variant variant = playlistWithSubstitutions.variants.get(0);
      assertThat(variant.format.codecs).isEqualTo("mp4a.40.5");
      assertThat(variant.url)
          .isEqualTo(Uri.parse("http://example.com/This/{$nested}/reference/shouldnt/work"));
    }
  
    @Test
    public void parseMultivariantPlaylist_withTtmlSubtitle() throws IOException {
      HlsMultivariantPlaylist playlistWithTtmlSubtitle =
          parseMultivariantPlaylist(PLAYLIST_URI, PLAYLIST_WITH_TTML_SUBTITLE);
      HlsMultivariantPlaylist.Variant variant = playlistWithTtmlSubtitle.variants.get(0);
      Format firstTextFormat = playlistWithTtmlSubtitle.subtitles.get(0).format;
      assertThat(firstTextFormat.id).isEqualTo("sub1:English");
      assertThat(firstTextFormat.containerMimeType).isEqualTo(MimeTypes.APPLICATION_M3U8);
      assertThat(firstTextFormat.sampleMimeType).isEqualTo(MimeTypes.APPLICATION_TTML);
      assertThat(variant.format.codecs).isEqualTo("stpp.ttml.im1t,mp4a.40.2,avc1.66.30");
    }
  
    @Test
    public void parseMultivariantPlaylist_withMatchingStreamInfUrls_success() throws IOException {
      HlsMultivariantPlaylist playlist =
          parseMultivariantPlaylist(PLAYLIST_URI, PLAYLIST_WITH_MATCHING_STREAM_INF_URLS);
      assertThat(playlist.variants).hasSize(4);
      assertThat(playlist.variants.get(0).format.metadata)
          .isEqualTo(
              createExtXStreamInfMetadata(
                  createVariantInfo(/* peakBitrate= */ 2227464, /* audioGroupId= */ "aud1"),
                  createVariantInfo(/* peakBitrate= */ 2448841, /* audioGroupId= */ "aud2"),
                  createVariantInfo(/* peakBitrate= */ 2256841, /* audioGroupId= */ "aud3")));
      assertThat(playlist.variants.get(1).format.metadata)
          .isEqualTo(
              createExtXStreamInfMetadata(
                  createVariantInfo(/* peakBitrate= */ 6453202, /* audioGroupId= */ "aud1"),
                  createVariantInfo(/* peakBitrate= */ 6482579, /* audioGroupId= */ "aud3")));
      assertThat(playlist.variants.get(2).format.metadata)
          .isEqualTo(
              createExtXStreamInfMetadata(
                  createVariantInfo(/* peakBitrate= */ 5054232, /* audioGroupId= */ "aud1"),
                  createVariantInfo(/* peakBitrate= */ 5275609, /* audioGroupId= */ "aud2")));
      assertThat(playlist.variants.get(3).format.metadata)
          .isEqualTo(
              createExtXStreamInfMetadata(
                  createVariantInfo(/* peakBitrate= */ 8399417, /* audioGroupId= */ "aud2"),
                  createVariantInfo(/* peakBitrate= */ 8207417, /* audioGroupId= */ "aud3")));
  
      assertThat(playlist.audios).hasSize(3);
      assertThat(playlist.audios.get(0).format.metadata)
          .isEqualTo(createExtXMediaMetadata(/* groupId= */ "aud1", /* name= */ "English"));
      assertThat(playlist.audios.get(1).format.metadata)
          .isEqualTo(createExtXMediaMetadata(/* groupId= */ "aud2", /* name= */ "English"));
      assertThat(playlist.audios.get(2).format.metadata)
          .isEqualTo(createExtXMediaMetadata(/* groupId= */ "aud3", /* name= */ "English"));
    }
  
    @Test
    public void testIFrameVariant() throws IOException {
      HlsMultivariantPlaylist playlist =
          parseMultivariantPlaylist(PLAYLIST_URI, PLAYLIST_WITH_IFRAME_VARIANTS);
      assertThat(playlist.variants).hasSize(5);
      for (int i = 0; i < 4; i++) {
        assertThat(playlist.variants.get(i).format.roleFlags).isEqualTo(0);
      }
      Variant iFramesOnlyVariant = playlist.variants.get(4);
      assertThat(iFramesOnlyVariant.format.bitrate).isEqualTo(1313400);
      assertThat(iFramesOnlyVariant.format.roleFlags & C.ROLE_FLAG_TRICK_PLAY)
          .isEqualTo(C.ROLE_FLAG_TRICK_PLAY);
    }
  
    private static Metadata createExtXStreamInfMetadata(HlsTrackMetadataEntry.VariantInfo... infos) {
      return new Metadata(
          new HlsTrackMetadataEntry(/* groupId= */ null, /* name= */ null, Arrays.asList(infos)));
    }
  
    private static Metadata createExtXMediaMetadata(String groupId, String name) {
      return new Metadata(new HlsTrackMetadataEntry(groupId, name, Collections.emptyList()));
    }
  
    private static HlsTrackMetadataEntry.VariantInfo createVariantInfo(
        int peakBitrate, String audioGroupId) {
      return new HlsTrackMetadataEntry.VariantInfo(
          /* averageBitrate= */ Format.NO_VALUE,
          /* peakBitrate= */ peakBitrate,
          /* videoGroupId= */ null,
          audioGroupId,
          /* subtitleGroupId= */ "sub1",
          /* captionGroupId= */ "cc1");
    }
  
    private static HlsMultivariantPlaylist parseMultivariantPlaylist(
        String uri, String playlistString) throws IOException {
      Uri playlistUri = Uri.parse(uri);
      ByteArrayInputStream inputStream =
          new ByteArrayInputStream(playlistString.getBytes(Charsets.UTF_8));
      return (HlsMultivariantPlaylist) new HlsPlaylistParser().parse(playlistUri, inputStream);
    }
  }
  