lib/silence_generator.ex

defmodule Membrane.SilenceGenerator do
  @moduledoc """
  Element responsible for generating silence as raw audio.
  """

  use Membrane.Source

  alias Membrane.{Buffer, Time}
  alias Membrane.RawAudio

  def_options caps: [
                type: :struct,
                spec: RawAudio.t(),
                description:
                  "Audio caps of generated samples (`t:Membrane.Caps.Audio.RawAudio.t/0`)"
              ],
              duration: [
                type: :timeout,
                spec: Time.t() | :infinity,
                description: "Duration of the generated silent samples"
              ],
              frames_per_buffer: [
                type: :integer,
                spec: pos_integer(),
                description: """
                Assumed number of raw audio frames in each buffer.
                Used when converting demand from buffers into bytes.
                """,
                default: 2048
              ]

  def_output_pad :output, caps: RawAudio

  @impl true
  def handle_init(opts) do
    state =
      opts
      |> Map.from_struct()
      |> Map.put(:passed_time, 0)

    {:ok, state}
  end

  @impl true
  def handle_prepared_to_playing(_context, %{caps: caps} = state) do
    {{:ok, caps: {:output, caps}}, state}
  end

  @impl true
  def handle_demand(:output, size, :bytes, _ctx, %{caps: caps} = state) do
    time = RawAudio.bytes_to_time(size, caps)
    do_handle_demand(time, state)
  end

  def handle_demand(:output, buffers, :buffers, _ctx, state) do
    %{caps: caps, frames_per_buffer: frames_per_buffer} = state

    time = buffers * RawAudio.frames_to_time(frames_per_buffer, caps)
    do_handle_demand(time, state)
  end

  defp do_handle_demand(
         time,
         %{caps: caps, duration: :infinity, passed_time: passed_time} = state
       ) do
    buffer = %Buffer{payload: RawAudio.silence(caps, time), pts: passed_time}
    {{:ok, buffer: {:output, buffer}}, %{state | passed_time: passed_time + time}}
  end

  defp do_handle_demand(time, state) do
    %{caps: caps, duration: duration, passed_time: passed_time} = state

    if passed_time + time < duration do
      buffer = %Buffer{payload: RawAudio.silence(caps, time), pts: passed_time}
      {{:ok, buffer: {:output, buffer}}, %{state | passed_time: passed_time + time}}
    else
      buffer = %Buffer{payload: RawAudio.silence(caps, duration - passed_time), pts: passed_time}
      {{:ok, buffer: {:output, buffer}, end_of_stream: :output}, %{state | passed_time: duration}}
    end
  end
end