Skip to content
Roderick van Domburg edited this page Nov 22, 2024 · 19 revisions

With librespot --onevent=/path/to/my/event/script/program users can subscribe to non-blocking events. By also passing emit-sink-events users can also subscribe to blocking sink events with the same script/program. The purpose of blocking sink events is to block the player thread to allow for something to be done before the sink is opened or after it is closed.

The type of script/program you use to handle events is completely up to you, librespot simply runs the script/program every time an event is fired and passes events via environment variables.

Event Keys and Values

PLAYER_EVENT will contain the name of the Event. Additional environment variables will follow depending on the Event.

The mapping is as follows:

Current; >= 0.5.0

Non-blocking Events

Non-blocking events do not block librespot threads but are buffered and blocking to themselves so that subsequent events are not fired until the event script/program exits after processing the previous event to help guarantee that event scripts/programs do not process events out of order.

Session Connected / Session Disconnected

Fired when a user connects to / disconnects from librespot.

Session Connected events will be followed immediately by Client Changed, Volume Changed, Auto Play Changed, Filter Explicit Content, Shuffle Changed, and Repeat Changed events so that event consumers will have the initial state of the session.

These Events both share common fields, just PLAYER_EVENT is different.

Key Value Description
PLAYER_EVENT session_connected / session_disconnected Name of the Event
USER_NAME User Name Session User Name (really an ID not a display name)
CONNECTION_ID Connection ID Session Connection ID

Session Client Changed

Fired when the Client changes or immediately after a Session Connected event.

Key Value Description
PLAYER_EVENT session_client_changed Name of the Event
CLIENT_ID Client ID ID of the Client
CLIENT_NAME Client Name Name of the Client
CLIENT_BRAND_NAME Client Brand Name Brand Name of the Client
CLIENT_MODEL_NAME Client Model Name Model Name of the Client

Volume Changed

Fired when the Volume changes or immediately after a Session Connected event.

Key Value Description
PLAYER_EVENT volume_changed Name of the Event
VOLUME volume Volume 0 - 65535

Shuffle Changed

Fired when the Shuffle toggle is flipped or immediately after a Session Connected event.

Key Value Description
PLAYER_EVENT shuffle_changed Name of the Event
SHUFFLE True/False State of the Shuffle toggle

Repeat Changed

Fired when the Repeat toggle is flipped or immediately after a Session Connected event.

Key Value Description
PLAYER_EVENT repeat_changed Name of the Event
REPEAT True/False State of the Repeat toggle

Auto Play Changed

Fired when the Auto Play toggle is flipped or immediately after a Session Connected event.

Key Value Description
PLAYER_EVENT auto_play_changed Name of the Event
AUTO_PLAY True/False State of the Auto Play toggle

Filter Explicit Content Changed

Fired when the Filter Explicit Content toggle is flipped or immediately after a Session Connected event.

Key Value Description
PLAYER_EVENT filter_explicit_content_changed Name of the Event
FILTER True/False State of the Filter Explicit Content toggle

Track Changed

Fired when the Track changes.

Track Changed has Common Fields shared by both Tracks and Episodes and Fields unique to Tracks and Episodes.

Common Fields
Key Value Description
PLAYER_EVENT track_changed Name of the Event
ITEM_TYPE Track / Episode Track or Episode
TRACK_ID Spotify Track ID ID of the Track
URI URI URI of the Track
NAME Name Name of the Track
DURATION_MS Milliseconds Duration in ms
IS_EXPLICIT True/False If the Track is Explicit
LANGUAGE Languages \n separated list of Languages
COVERS Cover urls \n separated list of Cover urls from largest to smallest in size
Track Item Type Specific Fields
Key Value Description
NUMBER Track Number Number of the Track as it appears on the album
DISC_NUMBER Disc Number Disc Number of the Track as it appears on the album
POPULARITY Popularity Popularity of the Track 0 - 100
ALBUM Album Album the Track appears on
ARTISTS Artists \n separated list of the artists that appear on the Track
ALBUM_ARTISTS Album Artists \n separated list of the artists of the album
Episode Item Type Specific Fields
Key Value Description
SHOW_NAME Show Name Name of the Show
PUBLISH_TIME Unix Timestamp Unix Timestamp of the Publish Time
DESCRIPTION Description Description of the Show/Episode

Playing / Paused / Seeked / Position Correction

Playing / Paused are fired when librespot is in the respectively state.

Seeked / Position Correction are fired when the position changes due to a seek or an internal librespot position correction. Track position can be assumed to be advancing at a normal rate otherwise.

These Events all share common fields, just PLAYER_EVENT is different.

Key Value Description
PLAYER_EVENT playing / paused / seeked / position_correction Name of the Event
TRACK_ID Spotify Track ID ID of the Track
POSITION_MS Milliseconds Position in ms

Unavailable / End of Track / Preload Next / Preloading / Loading / Stopped

Unavailable / Preload Next / Preloading / Loading represent internal librespot states and are useful mainly for debugging.

Stopped is fired when librespot stops.

End of Track is fired at the end of a Track.

These Events all share common fields, just PLAYER_EVENT is different.

Key Value Description
PLAYER_EVENT unavailable / end_of_track / preload_next / preloading / loading / stopped Name of the Event
TRACK_ID Spotify Track ID ID of the Track

Event Flows

In this section we will focus on events that are the most useful to consumers.

A new user connects:

The Session Connected / Session Disconnected events contain a "username" field but it's not really suitable for display, it's really more of an ID. It will be something like 01234567.

Session Connected > Client Changed > Volume Changed > Auto Play Changed > Filter Explicit Content > Shuffle Changed > Repeat Changed

An example jsonified output when feed though the Example Event Handler Script:

{
    "event_time": "2022-09-28 20:14:10.695987",
    "event": "session_connected",
    "user_name": "XXXXXXXX",
    "connection_id": "XXXXXXXX"
}
{
    "event_time": "2022-09-28 20:14:10.718546",
    "event": "session_client_changed",
    "client_id": "XXXXXXXX",
    "client_name": "little-baby-war-machine",
    "client_brand_name": "spotify",
    "client_model_name": "PC desktop"
}
{
    "event_time": "2022-09-28 20:14:10.736467",
    "event": "volume_changed",
    "volume": "65535"
}
{
    "event_time": "2022-09-28 20:14:10.757426",
    "event": "auto_play_changed",
    "auto_play": "true"
}
{
    "event_time": "2022-09-28 20:14:10.775620",
    "event": "filter_explicit_content_changed",
    "filter": "false"
}
{
    "event_time": "2022-09-28 20:14:10.795816",
    "event": "shuffle_changed",
    "shuffle": "false"
}
{
    "event_time": "2022-09-28 20:14:10.817437",
    "event": "repeat_changed",
    "repeat": "false"
}

A user disconnects:

Session Disconnected

All previous Event info is now invalid.

A consumer should not count on the session picking up where it left off. It is best not to guess, but to just wait for more events.

The user switches devices:

For example they started the session on the desktop client and then later used their phone as a control point.

Client Changed

A new Track starts:

Track Changed > Playing / Paused

An example jsonified output when feed though the Example Event Handler Script:

{
    "event_time": "2022-09-28 20:15:28.674763",
    "event": "track_changed",
    "common_metadata_fields": {
        "item_type": "Track",
        "track_id": "10Nmj3JCNoMeBQ87uw5j8k",
        "uri": "spotify:track:10Nmj3JCNoMeBQ87uw5j8k",
        "name": "Dani California",
        "duration_ms": "282160",
        "is_explicit": "false",
        "language": [
            "en"
        ],
        "covers": [
            "https://i.scdn.co/image/ab67616d0000b27309fd83d32aee93dceba78517",
            "https://i.scdn.co/image/ab67616d00001e0209fd83d32aee93dceba78517",
            "https://i.scdn.co/image/ab67616d0000485109fd83d32aee93dceba78517"
        ]
    },
    "track_metadata_fields": {
        "number": "1",
        "disc_number": "1",
        "popularity": "78",
        "album": "Stadium Arcadium",
        "artists": [
            "Red Hot Chili Peppers"
        ],
        "album_artists": [
            "Red Hot Chili Peppers"
        ]
    }
}
{
    "event_time": "2022-09-28 20:15:28.694878",
    "event": "playing",
    "track_id": "10Nmj3JCNoMeBQ87uw5j8k",
    "position_ms": "0"
}

A Track ends:

End of Track

If there are tracks left in the playlist or Auto Play or Repeat is enabled End of Track will be immediately followed by Track Changed > Playing/Paused, otherwise Stopped.

Playback stops:

Stopped

All previous playback Event info is now invalid.

A consumer should not count on the user picking up where they left off. It is best not to guess, but to just wait for more events.

The user seeks or librespot corrects it's time:

These events can be treated as the same as far as the consumer is concerned.

The Track position has changed in some unpredictable way.

Seeked / Position Correction

Keeping track of time

The Track's duration can be found in the Track Changed Event. As mentioned above Playing / Paused / Seeked / Position Correction all contain position fields when those Events are fired you should update your current time accordingly.

Blocking Events

Blocking Events and behavior have not changed and are identical to that of the legacy behaviour, documented below.

Example

An example non-blocking Event Python script can be found Here.

Refer to the below Example for Blocking Events.

Legacy; < 0.5.0

Non-blocking Events

Non-blocking events are non-blocking in every sense. They do not block librespot threads in any way and the event script/program is run in it's own separate thread for each and every event. librespot's event handler does not wait for event scripts/programs to exit before it fires the next event. This can lead to data race situations if the events take a variable amount of time for the script/program to process. It is up to the user to keep track of event sequencing. This has been fixed in dev.

Changed

Key Value Description
PLAYER_EVENT changed Name of the Event
OLD_TRACK_ID Spotify Track ID ID of the previous Track
TRACK_ID Spotify Track ID ID of the new Track

Started

Key Value Description
PLAYER_EVENT started Name of the Event
TRACK_ID Spotify Track ID ID of the Track

Stopped

Key Value Description
PLAYER_EVENT stopped Name of the Event
TRACK_ID Spotify Track ID ID of the Track

Playing

Key Value Description
PLAYER_EVENT playing Name of the Event
TRACK_ID Spotify Track ID ID of the Track
DURATION_MS Milliseconds Duration in ms
POSITION_MS Milliseconds Position in ms

Paused

Key Value Description
PLAYER_EVENT paused Name of the Event
TRACK_ID Spotify Track ID ID of the Track
DURATION_MS Milliseconds Duration in ms
POSITION_MS Milliseconds Position in ms

Preloading

Key Value Description
PLAYER_EVENT preloading Name of the Event
TRACK_ID Spotify Track ID ID of the Track

Volume Set

Key Value Description
PLAYER_EVENT volume_set Name of the Event
VOLUME volume Volume 0 - 65535

Blocking Events

Blocking events are blocking in every sense. They block librespot's player thread and also therefore block themselves. librespot's player thread will not unblock until the event script/program exits. Blocking events will not be fired by default for that reason. Use blocking events with care.

Running

Key Value Description
PLAYER_EVENT sink Name of the Event
SINK_STATUS running The sink is about to be opened

Temporarily Closed (Transient State)

Key Value Description
PLAYER_EVENT sink Name of the Event
SINK_STATUS temporarily_closed The sink has closed, but more than likely will be reopened very shortly

Closed

Key Value Description
PLAYER_EVENT sink Name of the Event
SINK_STATUS closed The sink has closed

Example

Here is an example Python skeleton to further illustrate:

#!/usr/bin/python3

import os

# Non-blocking Events
if os.environ['PLAYER_EVENT'] == 'changed':
    old_track_id = os.environ['OLD_TRACK_ID']
    new_track_id = os.environ['TRACK_ID']
    # do stuff

elif os.environ['PLAYER_EVENT'] == 'started':
    track_id = os.environ['TRACK_ID']
    # do suff

elif os.environ['PLAYER_EVENT'] == 'stopped':
    track_id = os.environ['TRACK_ID']
    # do stuff
   
elif os.environ['PLAYER_EVENT'] == 'playing':
    track_id = os.environ['TRACK_ID']
    track_duration_ms = os.environ['DURATION_MS']
    track_position_ms = os.environ['POSITION_MS']
    # do stuff

elif os.environ['PLAYER_EVENT'] == 'paused':
    track_id = os.environ['TRACK_ID']
    track_duration_ms = os.environ['DURATION_MS']
    track_position_ms = os.environ['POSITION_MS']
    # do stuff

elif os.environ['PLAYER_EVENT'] == 'preloading':
    track_id = os.environ['TRACK_ID']
    # do stuff

elif os.environ['PLAYER_EVENT'] == 'volume_set':
    volume = os.environ['VOLUME']
    # do stuff

# Blocking Events
elif os.environ['PLAYER_EVENT'] == 'sink':
    status = os.environ['SINK_STATUS']

    if status == 'running':
        # do stuff
    elif status == 'temporarily_closed':
        # do stuff
    elif status == 'closed':
        # do stuff
Clone this wiki locally