lib/membrane_http_adaptive_stream/bandwidth_calculator.ex

defmodule Membrane.HTTPAdaptiveStream.BandwidthCalculator do
  @moduledoc """
  Functions to calculate multimedia track bandwidth according to: https://datatracker.ietf.org/doc/html/rfc8216#section-4.3.4.2
  """

  use Ratio

  alias Membrane.HTTPAdaptiveStream.Manifest.Track
  alias Membrane.Time

  @spec calculate_bandwidth(Track.t()) :: integer()
  def calculate_bandwidth(track) do
    target_duration = Ratio.ceil(track.target_segment_duration / Time.second()) |> trunc()
    segments_sequences = generate_subsequences(track.segments |> Enum.to_list())

    segments_meta =
      segments_sequences
      |> Enum.map(
        &{
          Enum.map(&1, fn sg -> 8 * sg.bytes_size end) |> Enum.sum(),
          Enum.map(&1, fn sg -> Ratio.to_float(sg.duration / Time.second()) end) |> Enum.sum()
        }
      )

    # According to HLS rfc only segment subsequences with duration between 0.5 and 1.5 of target duration should be
    # considered
    valid_segments_meta =
      segments_meta
      |> Enum.filter(fn {_bits_size, duration} ->
        duration >= 0.5 * target_duration and duration <= 1.5 * target_duration
      end)

    valid_segments_meta
    |> Enum.map(fn {bits_size, duration} -> bits_size / duration end)
    |> Enum.max()
    |> trunc()
  end

  # Generates all contiguous subsequences of a list. This is needed because rfc defines bandwidth
  # in HLS as the highest size to duration ratio of all contiguous subsequences of media track segments.
  defp generate_subsequences(sequence) do
    n = length(sequence)
    subsequence_ranges = for(i <- 0..(n - 1), do: for(j <- 0..i, do: {j, i})) |> List.flatten()
    Enum.map(subsequence_ranges, fn {i, j} -> Enum.slice(sequence, i..j) end)
  end
end