lib/membrane_http_adaptive_stream/bandwidth_calculator.ex

defmodule Membrane.HTTPAdaptiveStream.BandwidthCalculator do
  @moduledoc false

  # Function to calculate multimedia track bandwidth
  # For a single track it comes down to finding a single segment with the highest bitrate, equal to the size in bits to duration ratio.

  use Numbers, overload_operators: true

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

  # Default value that is returned when track bandwidth calculation is impossible. High value is used since it is less
  # harmful to overestimate bandwidth and force HLS client to switch to track with lower bandwidth than to underestimate
  # and risk that client will use track with actual bandwidth that is beyond its capabilities.
  @default_bandwidth 2_560_000

  @spec calculate_max_bandwidth(Track.t(), integer()) :: integer()
  def calculate_max_bandwidth(track, segments_number \\ 20) do
    segments =
      track.segments
      |> Enum.to_list()
      |> Enum.take(-segments_number)
      |> Enum.filter(&Map.get(&1, :complete?, true))

    if Enum.empty?(segments) do
      @default_bandwidth
    else
      segments
      |> Enum.map(fn sg -> 8 * sg.size / (sg.duration / Time.second()) end)
      |> Enum.max(&Ratio.gte?/2)
      |> Ratio.trunc()
    end
  end

  @spec calculate_avg_bandwidth(Track.t(), integer()) :: integer()
  def calculate_avg_bandwidth(track, segments_number \\ 20) do
    segments =
      track.segments
      |> Enum.to_list()
      |> Enum.take(-segments_number)
      |> Enum.filter(&Map.get(&1, :complete?, true))

    if Enum.empty?(segments) do
      @default_bandwidth
    else
      segments
      |> Enum.reduce(Ratio.new(0), fn sg, ratio ->
        Ratio.new(8 * sg.size * Time.second())
        |> Numbers.div(sg.duration)
        |> Numbers.add(ratio)
      end)
      |> then(&(&1 / Enum.count(segments)))
      |> Ratio.trunc()
    end
  end
end