Skip to content

Commit

Permalink
Merge pull request #46 from spezifisch/scrobble
Browse files Browse the repository at this point in the history
Scrobbling Support
  • Loading branch information
wildeyedskies authored Oct 29, 2023
2 parents 3182c8d + d112c6c commit dc2d778
Show file tree
Hide file tree
Showing 4 changed files with 68 additions and 2 deletions.
3 changes: 2 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -47,10 +47,11 @@ or the directory in which the executible is placed.
[auth]
username = 'admin'
password = 'password'
plaintext = true # Use 'legacy' unsalted password auth. (default: false)
plaintext = true # Use 'legacy' unsalted password auth. (default: false)

[server]
host = 'https://your-subsonic-host.tld'
scrobble = true # Use Subsonic scrobbling for last.fm/ListenBrainz (default: false)
```

## Usage
Expand Down
17 changes: 17 additions & 0 deletions api.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ type SubsonicConnection struct {
Password string
Host string
PlaintextAuth bool
Scrobble bool
Logger Logger
directoryCache map[string]SubsonicResponse
}
Expand Down Expand Up @@ -214,6 +215,22 @@ func (connection *SubsonicConnection) GetRandomSongs() (*SubsonicResponse, error
return resp, nil
}

func (connection *SubsonicConnection) ScrobbleSubmission(id string, isSubmission bool) (*SubsonicResponse, error) {
query := defaultQuery(connection)
query.Set("id", id)

// optional field, false for "now playing", true for "submission"
query.Set("submission", strconv.FormatBool(isSubmission))

requestUrl := connection.Host + "/rest/scrobble" + "?" + query.Encode()
resp, err := connection.getResponse("ScrobbleSubmission", requestUrl)
if err != nil {
connection.Logger.Printf("ScrobbleSubmission error: %v", err)
return resp, err
}
return resp, nil
}

func (connection *SubsonicConnection) GetStarred() (*SubsonicResponse, error) {
query := defaultQuery(connection)
requestUrl := connection.Host + "/rest/getStarred" + "?" + query.Encode()
Expand Down
49 changes: 48 additions & 1 deletion gui.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import (
"math"
"sort"
"strings"
"time"

"github.com/gdamore/tcell/v2"
"github.com/rivo/tview"
Expand Down Expand Up @@ -33,6 +34,7 @@ type Ui struct {
playlists []SubsonicPlaylist
connection *SubsonicConnection
player *Player
scrobbleTimer *time.Timer
}

func (ui *Ui) handleEntitySelected(directoryId string) {
Expand Down Expand Up @@ -465,6 +467,12 @@ func createUi(_ *[]SubsonicIndex, playlists *[]SubsonicPlaylist, connection *Sub
// Stores the song IDs
var starIdList = map[string]struct{}{}

// create reused timer to scrobble after delay
scrobbleTimer := time.NewTimer(0)
if !scrobbleTimer.Stop() {
<-scrobbleTimer.C
}

ui := Ui{
app: app,
pages: pages,
Expand All @@ -484,6 +492,7 @@ func createUi(_ *[]SubsonicIndex, playlists *[]SubsonicPlaylist, connection *Sub
playlists: *playlists,
connection: connection,
player: player,
scrobbleTimer: scrobbleTimer,
}

ui.addStarredToList()
Expand All @@ -499,6 +508,17 @@ func createUi(_ *[]SubsonicIndex, playlists *[]SubsonicPlaylist, connection *Sub
ui.logList.RemoveItem(0)
}
})

case <-scrobbleTimer.C:
// scrobble submission delay elapsed
paused, err := ui.player.IsPaused()
connection.Logger.Printf("scrobbler event: paused %v, err %v, qlen %d", paused, err, len(ui.player.Queue))
isPlaying := err == nil && !paused
if len(ui.player.Queue) > 0 && isPlaying {
// it's still playing, submit it
currentSong := ui.player.Queue[0]
ui.connection.ScrobbleSubmission(currentSong.Id, true)
}
}
}
}()
Expand Down Expand Up @@ -917,8 +937,35 @@ func (ui *Ui) handleMpvEvents() {
}
} else if e.Event_Id == mpv.EVENT_START_FILE {
ui.player.ReplaceInProgress = false
ui.startStopStatus.SetText("[::b]stmp: [green]playing " + ui.player.Queue[0].Title)
updateQueueList(ui.player, ui.queueList, ui.starIdList)

if len(ui.player.Queue) > 0 {
currentSong := ui.player.Queue[0]
ui.startStopStatus.SetText("[::b]stmp: [green]playing " + currentSong.Title)

if ui.connection.Scrobble {
// scrobble "now playing" event
ui.connection.ScrobbleSubmission(currentSong.Id, false)

// scrobble "submission" after song has been playing a bit
// see: https://www.last.fm/api/scrobbling
// A track should only be scrobbled when the following conditions have been met:
// The track must be longer than 30 seconds. And the track has been played for
// at least half its duration, or for 4 minutes (whichever occurs earlier.)
if currentSong.Duration > 30 {
scrobbleDelay := currentSong.Duration / 2
if scrobbleDelay > 240 {
scrobbleDelay = 240
}
scrobbleDuration := time.Duration(scrobbleDelay) * time.Second

ui.scrobbleTimer.Reset(scrobbleDuration)
ui.connection.Logger.Printf("scrobbler: timer started, %v", scrobbleDuration)
} else {
ui.connection.Logger.Printf("scrobbler: track too short")
}
}
}
} else if e.Event_Id == mpv.EVENT_IDLE || e.Event_Id == mpv.EVENT_NONE {
continue
}
Expand Down
1 change: 1 addition & 0 deletions stmp.go
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,7 @@ func main() {
Password: viper.GetString("auth.password"),
Host: viper.GetString("server.host"),
PlaintextAuth: viper.GetBool("auth.plaintext"),
Scrobble: viper.GetBool("server.scrobble"),
Logger: logger,
directoryCache: make(map[string]SubsonicResponse),
}
Expand Down

0 comments on commit dc2d778

Please sign in to comment.