lib/membrane_file/source.ex

defmodule Membrane.File.Source do
  @moduledoc """
  Element that reads chunks of data from given file and sends them as buffers
  through the output pad.
  """

  use Membrane.Source
  alias Membrane.Buffer
  alias Membrane.File.CommonFile

  import Mockery.Macro

  def_options location: [type: :string, description: "Path to the file"],
              chunk_size: [
                type: :integer,
                spec: pos_integer,
                default: 2048,
                description: "Size of chunks being read"
              ]

  def_output_pad :output, caps: :any

  # Private API

  @impl true
  def handle_init(%__MODULE__{location: location, chunk_size: size}) do
    {:ok,
     %{
       location: location,
       chunk_size: size,
       fd: nil
     }}
  end

  @impl true
  def handle_stopped_to_prepared(_ctx, state), do: mockable(CommonFile).open(:read, state)

  @impl true
  def handle_demand(:output, _size, :buffers, _ctx, %{chunk_size: chunk_size} = state),
    do: supply_demand(chunk_size, [redemand: :output], state)

  def handle_demand(:output, size, :bytes, _ctx, state),
    do: supply_demand(size, [], state)

  def supply_demand(size, redemand, %{fd: fd} = state) do
    with <<payload::binary>> <- fd |> mockable(CommonFile).binread(size) do
      {{:ok, [buffer: {:output, %Buffer{payload: payload}}] ++ redemand}, state}
    else
      :eof -> {{:ok, end_of_stream: :output}, state}
      {:error, reason} -> {{:error, {:read_file, reason}}, state}
    end
  end

  @impl true
  def handle_prepared_to_stopped(_ctx, state), do: mockable(CommonFile).close(state)
end