Lastfm scrobblers making library
Scrobbling is a way to send information about the music a user is listening to. A client is anything that plays music, such as desktop music players, mobile apps, websites, etc.
From scrobbling guide
We do not provide magic ∷ IO ()
to solve any scrobbling problem
you may encounter, but you can make working scrobbler simply by composing the relevant parts:
import Control.Scrobbler
import Control.Scrobbler.Algorithm.MPD
main ∷ IO ()
main = scrobbler $
scrobble credentials
. contest
. updateNowPlaying credentials
. candidate
Each part does roughly what its name says. But to actually explain this, we need to explain Scrobbler
first
I assume you are familiar with netwire, but if not, there is excerpt from it:
data Wire e m a b
= WGen (Time → a → m (Either e b, Wire e m a b))
...
type Scrobbler a b = Wire ScrobblerError IO a b
So, you can think of Scrobbler
as of glorified function of type a → IO b
that may "error" and also has some "internal state". So,
First, some helper types definitions (they are all just for descriptive names):
data PlayerStateChange a = Started a | Stopped -- isomorphic to Maybe
newtype Scrobble a = Scrobble a -- isomorphic to Identity
newtype Successes a = Successes [a] -- isomorphic to []
Next, the meat of scrobbling, - track information:
data Track = Track
{ start ∷ Int64 -- track start time
, title ∷ Text -- title
, artist ∷ Text -- artist
, album ∷ Text -- album title
, length ∷ Int64 -- length
, local ∷ Int64 -- local time
}
Okay, we are all set, let's describe provided Scrobbler
s:
candidate ∷ Scrobbler Time (PlayerStateChange Track)
That wire repeatedly asks player (in our example above, that player would be MPD) if its state was changed.
Player may respond with 3 different answers:
- No, my state wasn't changed
- Yes, I started playing some track
- Yes, I stopped playing
Update last.fm profile status
updateNowPlaying ∷ Credentials → Scrobbler (PlayerStateChange Track) (PlayerStateChange Track)`
That wire notifies last.fm about changes in player state (only if it worths it: surely nobody wants to know you stopped playing music) and passes its argument further
contest ∷ Scrobbler (PlayerStateChange Track) (Scrobble Track)
That wire has internal state: previously played track. If player started the new one or stopped, we want to know if that previously played track is worth scrobbling. There are 2 choices there:
- Yes. So we return the previous track
- No. So nothing happens
scrobble ∷ Credentials → Scrobbler (Scrobble Track) (Successes Track)
That wire tries to scrobble incoming Track
and also all previous failures (if any)
Yes, scrobbling may fail for variety of reasons, so we need to store failures for some time.
Now example scrobbler should be clear: (scrobbler
is just a kind of main loop)
main ∷ IO ()
main = scrobbler $
scrobble credentials
. contest
. updateNowPlaying credentials
. candidate
Casual example is basically this but also with some debug information included.
You can split your scrobbler in parts, if you don't like having your lastfm information on machine where player is started. Or, for example, you want to have one scrobbler for all players. Or something like that
-
Serialization
deserialize ∷ Serialize b ⇒ Scrobbler ByteString b serialize ∷ Serialize a ⇒ Scrobbler a ByteString
serialize
anddeserialize
wires help with serialization of any stuff you can get in the process of scrobbling -
Communication
Finally:
data Failures = Drop | Preserve data NetworkSettings = NetworkSettings { host ∷ HostName , port ∷ PortID , failures ∷ Failures } send ∷ NetworkSettings → Scrobbler ByteString () receive ∷ NetworkSettings → Scrobbler () ByteString
send
sends data to specifiedhost
andport
. Also it takes into account failure treating policy: if it's toDrop
failures, then everysend
is successful, otherwise it'sPreserve
and every failed sending accumulates data that it tries to resubmit on next activationreceive
only cares aboutport
part of settings