Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 20 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,8 @@ config :mopidy,
api_url: System.get_env("MOPIDY_API_URL")
```

If you want to be able to receive events via websockets you will also need to set `websocket_api_url`

## Usage

The online [documentation][doc] for the Mopidy HTTP API will give you a general
Expand All @@ -46,6 +48,24 @@ iex> search_results.artists
uri: "spotify:artist:4Z8W4fKeB5YxbusRsdQVPb"}]
```

To receive events:
```elixir
iex> Mopidy.Events.create_stream |> Enum.each(&IO.inspect/1)
{:ok,
%{"event" => "playback_state_changed", "new_state" => "playing",
"old_state" => "paused"}}
{:ok,
%{event: "track_playback_resumed", time_position: 1819,
tl_track: %Mopidy.TlTrack{__model__: "TlTrack", tlid: 658,
track: %Mopidy.Track{__model__: "Track",
album: %Mopidy.Album{__model__: "Album",
name: "It'll End In Tears (Remastered)",
uri: "spotify:album:6D6C7jGsJdzJpcEaMcxswR"},
artists: [%Mopidy.Artist{__model__: "Artist", name: "This Mortal Coil",
uri: "spotify:artist:5OK8j1JnhoBlivN32G7yOO"}],
name: "Song To The Siren (Remastered)",
uri: "spotify:track:0BPTTsnnfz44XmZn3EE0oo"}}}}
```
## License

MIT License, see [LICENSE](LICENSE) for details.
Expand Down
55 changes: 49 additions & 6 deletions lib/mopidy.ex
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,49 @@ defmodule Mopidy.Playlist do
}
end


defmodule Mopidy.Websocket do
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Modules should have a @moduledoc tag.

@moduledoc """
A Websocket connection to Mopidy
"""
defstruct connection: nil

@type t :: %__MODULE__{
connection: Socket.Web.t
}

def new do
%Mopidy.Websocket{connection: connect()}
end

def receive_next_event(%Mopidy.Websocket{connection: connection} = socket) when not is_nil(connection) do
case Socket.Web.recv(connection) do
{:ok, {:text, data}} -> {[parse_event(data)], socket}
_ -> {:halt, socket}
end
end

def receive_next_event(%Mopidy.Websocket{connection: nil} = socket) do
{:halt, socket}
end

def parse_event(data) do
Mopidy.Events.parse_event(Poison.decode!(data))
end

defp connect do
%URI{host: host, path: path, port: port} = URI.parse(mopidy_websocket_api_url())
case Socket.Web.connect {host, port || 80}, path: path || "" do
{:ok, conn} -> conn
_ -> nil
end
end

defp mopidy_websocket_api_url do
Application.get_env(:mopidy, :websocket_api_url)
end
end

defmodule Mopidy do
@moduledoc """
An HTTP client for Mopidy
Expand All @@ -96,7 +139,7 @@ defmodule Mopidy do
@request_timeout 5_000

def start(_type, _args) do
start
start()

import Supervisor.Spec, warn: false

Expand All @@ -111,7 +154,7 @@ defmodule Mopidy do
Returns string
"""
def process_url(endpoint) do
mopidy_api_url <> endpoint
mopidy_api_url() <> endpoint
end

def process_request_body(body) do
Expand All @@ -126,8 +169,8 @@ defmodule Mopidy do
Set our request headers for every request.
"""
def process_request_headers(headers) do
Dict.put headers, :"User-Agent", "Mopidy/v1 mopidy-elixir/0.2.0"
Dict.put headers, :"Content-Type", "application/json"
Keyword.put headers, :"User-Agent", "Mopidy/v1 mopidy-elixir/0.2.0"
Keyword.put headers, :"Content-Type", "application/json"
end

@doc """
Expand All @@ -141,7 +184,7 @@ defmodule Mopidy do
def api_request(data \\ %{}) do
body = Map.merge(%{id: "1", jsonrpc: "2.0"}, data)

with %HTTPotion.Response{body: body} <- Mopidy.post(nil, [body: body, timeout: mopidy_request_timeout]) do
with %HTTPotion.Response{body: body} <- Mopidy.post(nil, [body: body, timeout: mopidy_request_timeout()]) do
{:ok, body}
else
%HTTPotion.ErrorResponse{message: message} -> {:error, message}
Expand Down Expand Up @@ -172,7 +215,7 @@ defmodule Mopidy do
def parse_data(:value, body), do: {:ok, body["value"]}
def parse_data(:result, body), do: {:ok, body["result"]}
def parse_data(:uri, body), do: {:ok, parse_data(:uri, body["result"], %{})}
def parse_data(data_type, body), do: {:ok, parse_data(data_type, body["result"], [])}
def parse_data(data_type, body), do: {:ok, parse_data(data_type, body["result"], [])}

# List parsing
def parse_data(_data_type, nil, _accumulator), do: nil
Expand Down
47 changes: 47 additions & 0 deletions lib/mopidy/events.ex
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
defmodule Mopidy.Events do
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Modules should have a @moduledoc tag.

@moduledoc """
Get a stream of Mopidy events
"""
alias Mopidy.{TlTrack, Playlist}

def create_stream do
Stream.resource(
fn -> Mopidy.Websocket.new end,
fn (socket) ->
Mopidy.Websocket.receive_next_event(socket)
end,
fn _ -> IO.puts("stream ended") end
)
end

# Entry point
def parse_event(%{"event" => event} = body), do: {:ok, parse_event(event, body)}

# Event parsing
def parse_event(event, datum_data) when event in ~w(track_playback_resumed track_playback_paused track_playback_ended) do
%{
event: event,
time_position: datum_data["time_position"],
tl_track: Mopidy.parse_data(%TlTrack{}, datum_data["tl_track"], %{})
}
end

def parse_event("track_playback_started" = event, datum_data) do
%{
event: event,
tl_track: Mopidy.parse_data(%TlTrack{}, datum_data["tl_track"], %{})
}
end

def parse_event("playlist_changed" = event, datum_data) do
%{
event: event,
playlist: Mopidy.parse_data(%Playlist{}, datum_data["playlist"], %{})
}
end

def parse_event(_, datum_data) do
datum_data
end

end
14 changes: 7 additions & 7 deletions mix.exs
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,9 @@ defmodule Mopidy.Mixfile do
app: :mopidy,
version: "0.3.0",
elixir: ">= 1.3.0",
deps: deps,
description: description,
package: package,
deps: deps(),
description: description(),
package: package(),
build_embedded: Mix.env == :prod,
start_permanent: Mix.env == :prod,
test_coverage: [tool: ExCoveralls],
Expand All @@ -34,13 +34,13 @@ defmodule Mopidy.Mixfile do
[
{:credo, "~> 0.4", only: [:dev, :test]},
{:dogma, "~> 0.1", only: :dev},
{:earmark, "~> 0.1", only: :dev},
{:earmark, "~> 1.2", only: :dev},
{:ex_doc, "~> 0.11", only: :dev},
{:excoveralls, "~> 0.4", only: :test},
{:httpotion, "~> 3.0.0"},
{:inch_ex, "~> 0.4", only: :docs},
{:httpotion, "~> 3.0"},
{:mix_test_watch, "~> 0.2", only: :test},
{:poison, "~> 2.1"}
{:poison, "~> 3.1"},
{:socket, "~> 0.3"}
]
end

Expand Down
44 changes: 23 additions & 21 deletions mix.lock
Original file line number Diff line number Diff line change
@@ -1,21 +1,23 @@
%{"bunt": {:hex, :bunt, "0.1.6", "5d95a6882f73f3b9969fdfd1953798046664e6f77ec4e486e6fafc7caad97c6f", [:mix], []},
"certifi": {:hex, :certifi, "0.4.0", "a7966efb868b179023618d29a407548f70c52466bf1849b9e8ebd0e34b7ea11f", [:rebar3], []},
"credo": {:hex, :credo, "0.4.5", "5c5daaf50a2a96068c0f21b6fbd382d206702efa8836a946eeab0b8ac25f5f22", [:mix], [{:bunt, "~> 0.1.6", [hex: :bunt, optional: false]}]},
"dogma": {:hex, :dogma, "0.1.7", "927f76a89a809db96e0983b922fc899f601352690aefa123529b8aa0c45123b2", [:mix], [{:poison, ">= 1.0.0", [hex: :poison, optional: false]}]},
"earmark": {:hex, :earmark, "0.2.1", "ba6d26ceb16106d069b289df66751734802777a3cbb6787026dd800ffeb850f3", [:mix], []},
"ex_doc": {:hex, :ex_doc, "0.12.0", "b774aabfede4af31c0301aece12371cbd25995a21bb3d71d66f5c2fe074c603f", [:mix], [{:earmark, "~> 0.2", [hex: :earmark, optional: false]}]},
"excoveralls": {:hex, :excoveralls, "0.5.5", "d97b6fc7aa59c5f04f2fa7ec40fc0b7555ceea2a5f7e7c442aad98ddd7f79002", [:mix], [{:hackney, ">= 0.12.0", [hex: :hackney, optional: false]}, {:exjsx, "~> 3.0", [hex: :exjsx, optional: false]}]},
"exjsx": {:hex, :exjsx, "3.2.0", "7136cc739ace295fc74c378f33699e5145bead4fdc1b4799822d0287489136fb", [:mix], [{:jsx, "~> 2.6.2", [hex: :jsx, optional: false]}]},
"fs": {:hex, :fs, "0.9.2", "ed17036c26c3f70ac49781ed9220a50c36775c6ca2cf8182d123b6566e49ec59", [:rebar], []},
"hackney": {:hex, :hackney, "1.6.0", "8d1e9440c9edf23bf5e5e2fe0c71de03eb265103b72901337394c840eec679ac", [:rebar3], [{:ssl_verify_fun, "1.1.0", [hex: :ssl_verify_fun, optional: false]}, {:mimerl, "1.0.2", [hex: :mimerl, optional: false]}, {:metrics, "1.0.1", [hex: :metrics, optional: false]}, {:idna, "1.2.0", [hex: :idna, optional: false]}, {:certifi, "0.4.0", [hex: :certifi, optional: false]}]},
"httpoison": {:hex, :httpoison, "0.8.3", "b675a3fdc839a0b8d7a285c6b3747d6d596ae70b6ccb762233a990d7289ccae4", [:mix], [{:hackney, "~> 1.6.0", [hex: :hackney, optional: false]}]},
"httpotion": {:hex, :httpotion, "3.0.0", "4ce0af28f4254bdd0457d0fe065a83787751e0a5b5f5d27030948253be38df5c", [:mix], [{:ibrowse, "~> 4.2", [hex: :ibrowse, optional: false]}]},
"ibrowse": {:hex, :ibrowse, "4.2.2", "b32b5bafcc77b7277eff030ed32e1acc3f610c64e9f6aea19822abcadf681b4b", [:rebar3], []},
"idna": {:hex, :idna, "1.2.0", "ac62ee99da068f43c50dc69acf700e03a62a348360126260e87f2b54eced86b2", [:rebar3], []},
"inch_ex": {:hex, :inch_ex, "0.5.3", "39f11e96181ab7edc9c508a836b33b5d9a8ec0859f56886852db3d5708889ae7", [:mix], [{:poison, "~> 1.5 or ~> 2.0", [hex: :poison, optional: false]}]},
"jsx": {:hex, :jsx, "2.6.2", "213721e058da0587a4bce3cc8a00ff6684ced229c8f9223245c6ff2c88fbaa5a", [:mix, :rebar], []},
"metrics": {:hex, :metrics, "1.0.1", "25f094dea2cda98213cecc3aeff09e940299d950904393b2a29d191c346a8486", [:rebar3], []},
"mimerl": {:hex, :mimerl, "1.0.2", "993f9b0e084083405ed8252b99460c4f0563e41729ab42d9074fd5e52439be88", [:rebar3], []},
"mix_test_watch": {:hex, :mix_test_watch, "0.2.6", "9fcc2b1b89d1594c4a8300959c19d50da2f0ff13642c8f681692a6e507f92cab", [:mix], [{:fs, "~> 0.9.1", [hex: :fs, optional: false]}]},
"poison": {:hex, :poison, "2.2.0", "4763b69a8a77bd77d26f477d196428b741261a761257ff1cf92753a0d4d24a63", [:mix], []},
"ssl_verify_fun": {:hex, :ssl_verify_fun, "1.1.0", "edee20847c42e379bf91261db474ffbe373f8acb56e9079acb6038d4e0bf414f", [:rebar, :make], []}}
%{
"bunt": {:hex, :bunt, "0.2.0", "951c6e801e8b1d2cbe58ebbd3e616a869061ddadcc4863d0a2182541acae9a38", [:mix], [], "hexpm"},
"certifi": {:hex, :certifi, "2.0.0", "a0c0e475107135f76b8c1d5bc7efb33cd3815cb3cf3dea7aefdd174dabead064", [:rebar3], [], "hexpm"},
"credo": {:hex, :credo, "0.8.10", "261862bb7363247762e1063713bb85df2bbd84af8d8610d1272cd9c1943bba63", [:mix], [{:bunt, "~> 0.2.0", [hex: :bunt, repo: "hexpm", optional: false]}], "hexpm"},
"dogma": {:hex, :dogma, "0.1.16", "3c1532e2f63ece4813fe900a16704b8e33264da35fdb0d8a1d05090a3022eef9", [:mix], [{:poison, ">= 2.0.0", [hex: :poison, repo: "hexpm", optional: false]}], "hexpm"},
"earmark": {:hex, :earmark, "1.2.4", "99b637c62a4d65a20a9fb674b8cffb8baa771c04605a80c911c4418c69b75439", [:mix], [], "hexpm"},
"ex_doc": {:hex, :ex_doc, "0.18.1", "37c69d2ef62f24928c1f4fdc7c724ea04aecfdf500c4329185f8e3649c915baf", [:mix], [{:earmark, "~> 1.1", [hex: :earmark, repo: "hexpm", optional: false]}], "hexpm"},
"excoveralls": {:hex, :excoveralls, "0.8.0", "99d2691d3edf8612f128be3f9869c4d44b91c67cec92186ce49470ae7a7404cf", [:mix], [{:exjsx, ">= 3.0.0", [hex: :exjsx, repo: "hexpm", optional: false]}, {:hackney, ">= 0.12.0", [hex: :hackney, repo: "hexpm", optional: false]}], "hexpm"},
"exjsx": {:hex, :exjsx, "4.0.0", "60548841e0212df401e38e63c0078ec57b33e7ea49b032c796ccad8cde794b5c", [:mix], [{:jsx, "~> 2.8.0", [hex: :jsx, repo: "hexpm", optional: false]}], "hexpm"},
"fs": {:hex, :fs, "0.9.2", "ed17036c26c3f70ac49781ed9220a50c36775c6ca2cf8182d123b6566e49ec59", [:rebar], [], "hexpm"},
"hackney": {:hex, :hackney, "1.11.0", "4951ee019df102492dabba66a09e305f61919a8a183a7860236c0fde586134b6", [:rebar3], [{:certifi, "2.0.0", [hex: :certifi, repo: "hexpm", optional: false]}, {:idna, "5.1.0", [hex: :idna, repo: "hexpm", optional: false]}, {:metrics, "1.0.1", [hex: :metrics, repo: "hexpm", optional: false]}, {:mimerl, "1.0.2", [hex: :mimerl, repo: "hexpm", optional: false]}, {:ssl_verify_fun, "1.1.1", [hex: :ssl_verify_fun, repo: "hexpm", optional: false]}], "hexpm"},
"httpotion": {:hex, :httpotion, "3.0.3", "17096ea1a7c0b2df74509e9c15a82b670d66fc4d66e6ef584189f63a9759428d", [:mix], [{:ibrowse, "~> 4.4", [hex: :ibrowse, repo: "hexpm", optional: false]}], "hexpm"},
"ibrowse": {:hex, :ibrowse, "4.4.0", "2d923325efe0d2cb09b9c6a047b2835a5eda69d8a47ed6ff8bc03628b764e991", [:rebar3], [], "hexpm"},
"idna": {:hex, :idna, "5.1.0", "d72b4effeb324ad5da3cab1767cb16b17939004e789d8c0ad5b70f3cea20c89a", [:rebar3], [{:unicode_util_compat, "0.3.1", [hex: :unicode_util_compat, repo: "hexpm", optional: false]}], "hexpm"},
"jsx": {:hex, :jsx, "2.8.3", "a05252d381885240744d955fbe3cf810504eb2567164824e19303ea59eef62cf", [:mix, :rebar3], [], "hexpm"},
"metrics": {:hex, :metrics, "1.0.1", "25f094dea2cda98213cecc3aeff09e940299d950904393b2a29d191c346a8486", [:rebar3], [], "hexpm"},
"mimerl": {:hex, :mimerl, "1.0.2", "993f9b0e084083405ed8252b99460c4f0563e41729ab42d9074fd5e52439be88", [:rebar3], [], "hexpm"},
"mix_test_watch": {:hex, :mix_test_watch, "0.5.0", "2c322d119a4795c3431380fca2bca5afa4dc07324bd3c0b9f6b2efbdd99f5ed3", [:mix], [{:fs, "~> 0.9.1", [hex: :fs, repo: "hexpm", optional: false]}], "hexpm"},
"poison": {:hex, :poison, "3.1.0", "d9eb636610e096f86f25d9a46f35a9facac35609a7591b3be3326e99a0484665", [:mix], [], "hexpm"},
"socket": {:hex, :socket, "0.3.12", "4a6543815136503fee67eff0932da1742fad83f84c49130c854114153cc549a6", [:mix], [], "hexpm"},
"ssl_verify_fun": {:hex, :ssl_verify_fun, "1.1.1", "28a4d65b7f59893bc2c7de786dec1e1555bd742d336043fe644ae956c3497fbe", [:make, :rebar], [], "hexpm"},
"unicode_util_compat": {:hex, :unicode_util_compat, "0.3.1", "a1f612a7b512638634a603c8f401892afbf99b8ce93a45041f8aaca99cadb85e", [:rebar3], [], "hexpm"},
}