lib/exshome_player/services/mpv_server.ex

defmodule ExshomePlayer.Services.MpvServer do
  @moduledoc """
  Starts MPV server.
  """
  use Exshome.Dependency.GenServerDependency, name: "mpv_server"

  @player_folder "player"
  @music_folder Path.join(@player_folder, "music")

  def restart do
    call(:restart)
  end

  @impl GenServerDependency
  def on_init(%DependencyState{} = state) do
    start_mpv_server(state)
  end

  @impl GenServerDependency
  def handle_call(:restart, _from, %DependencyState{} = state) do
    {:reply, :exec.kill(state.data.server_pid, 9), state}
  end

  @impl GenServerDependency
  def handle_info(
        {:EXIT, server_pid, _reason},
        %DependencyState{data: %{server_pid: server_pid}} = state
      ) do
    state =
      state
      |> update_value(Dependency.NotReady)
      |> start_mpv_server()

    {:noreply, state}
  end

  @spec start_mpv_server(DependencyState.t()) :: DependencyState.t()
  defp start_mpv_server(%DependencyState{} = state) do
    case find_mpv_executable() do
      {:ok, program} ->
        {:ok, pid, _ospid} =
          program
          |> mpv_server_command()
          |> :exec.run_link([
            {:group, 0},
            :kill_group,
            :stdout,
            :stderr
          ])

        state
        |> update_data(fn _ -> %{server_pid: pid} end)
        |> update_value(:started)

      {:error, :not_found} ->
        update_value(state, Dependency.NotReady)
    end
  end

  def socket_path do
    Exshome.FileUtils.get_of_create_folder!(@player_folder)
    |> Path.join("mpv_socket")
  end

  def music_folder do
    Exshome.FileUtils.get_of_create_folder!(@music_folder)
  end

  @spec find_mpv_executable() :: {:ok, charlist()} | {:error, :not_found}
  defp find_mpv_executable do
    case System.find_executable("mpv") do
      nil -> {:error, :not_found}
      path -> {:ok, String.to_charlist(path)}
    end
  end

  defp mpv_server_command(program) when is_list(program) do
    [
      program,
      '--no-video',
      '--idle',
      '--no-cache',
      '--no-terminal',
      '--input-ipc-server=#{socket_path()}'
    ]
  end

  @hook_module Application.compile_env(:exshome, :mpv_server_hook_module)
  if @hook_module do
    defoverridable(mpv_server_command: 1)
    defdelegate mpv_server_command(program), to: @hook_module

    defoverridable(find_mpv_executable: 0)
    defdelegate find_mpv_executable(), to: @hook_module
  end
end