zmb3 / spotify

A Go wrapper for the Spotify Web API
Apache License 2.0
1.39k stars 291 forks source link

Segmentation violation when using PlayOpt #68

Closed jess-sch closed 6 years ago

jess-sch commented 6 years ago

client.PlayOpt(&spotify.PlayOptions{PlaybackContext: &spotify.URI("SOME_URI")}) always results in a crash. Am I stupid or is this a bug?

[signal SIGSEGV: segmentation violation code=0x1 addr=0x10 pc=0x644025]

goroutine 19 [running]:
github.com/zmb3/spotify.(*Client).PlayOpt(0x0, 0xc420498030, 0x0, 0x0)
    /home/lumi/go/src/github.com/zmb3/spotify/player.go:286 +0x55
main.mgmt(0x0)
    /home/lumi/Projects/spotiman/mgmt.go:50 +0xdd
created by main.main
    /home/lumi/Projects/spotiman/main.go:39 +0x7a
jess-sch commented 6 years ago

Side note: Same thing happens when I use URIs with a slice containing one URI instead of PlaybackContext

zmb3 commented 6 years ago

You're surely not stupid! Can you share a little more of your main.go?

jess-sch commented 6 years ago

main.go basically looks like this (mostly a copy-paste of that player example):

//       - Use "http://localhost:8080/callback" as the redirect URI
//  2. Set the SPOTIFY_ID environment variable to the client ID you got in step 1.
//  3. Set the SPOTIFY_SECRET environment variable to the client secret from step 1.
package main

import (
    "fmt"
    "log"
    "net/http"

    "github.com/zmb3/spotify"
)

const redirectURI = "http://localhost:8080/callback"

var (
    auth  = spotify.NewAuthenticator(redirectURI, spotify.ScopeUserReadCurrentlyPlaying, spotify.ScopeUserReadPlaybackState, spotify.ScopeUserModifyPlaybackState)
    ch    = make(chan *spotify.Client)
    state = "abc123"
)

var queue []spotify.SimpleTrack

func main() {
    // We'll want these variables sooner rather than later
    var client *spotify.Client
    var playerState *spotify.PlayerState
    go mgmt(client)
    http.HandleFunc("/callback", completeAuth)

    http.HandleFunc("/queue/", func(w http.ResponseWriter, r *http.Request) {
        [...]
    })

    http.HandleFunc("/add/", func(w http.ResponseWriter, r *http.Request) {
        [...]
    })

    http.HandleFunc("/search/", func(w http.ResponseWriter, r *http.Request) {
        [...]
    })

    http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
        [...]
    })

    go func() {
        url := auth.AuthURL(state)
        fmt.Println("Please log in to Spotify by visiting the following page in your browser:", url)

        // wait for auth to complete
        client = <-ch

        // use the client to make calls that require authorization
        user, err := client.CurrentUser()
        if err != nil {
            log.Fatal(err)
        }
        fmt.Println("You are logged in as:", user.ID)

        playerState, err = client.PlayerState()
        if err != nil {
            log.Fatal(err)
        }
        fmt.Printf("Found your %s (%s)\n", playerState.Device.Type, playerState.Device.Name)
    }()

    http.ListenAndServe(":8080", nil)

}

func completeAuth(w http.ResponseWriter, r *http.Request) {
    tok, err := auth.Token(state, r)
    if err != nil {
        http.Error(w, "Couldn't get token", http.StatusForbidden)
        log.Fatal(err)
    }
    if st := r.FormValue("state"); st != state {
        http.NotFound(w, r)
        log.Fatalf("State mismatch: %s != %s\n", st, state)
    }
    // use the token to get an authenticated client
    client := auth.NewClient(tok)
    w.Header().Set("Content-Type", "text/html")
    w.Write([]byte(`<meta http-equiv="refresh" content="0; url=/" />`))
    ch <- &client
}
jess-sch commented 6 years ago

But I think you may be more interested in the function that actually tells spotify to play the music:

for true{
if len(queue) > 0 {
client.PlayOpt(&spotify.PlayOptions{PlayContext: &queue[0].URI})
time.Sleep(time.Second * time.Duration(queue[0].Duration)
queue = queue[1:]
}else{
time.Sleep(time.Second * 5)
}
}

(Queue is a global []spotify.SimpleTrack)

zmb3 commented 6 years ago

My best guess is that you're sharing the client variable across goroutines without proper synchronization. It may help you use the -race flag to help diagnose data races.

jess-sch commented 6 years ago

So now I was able to fix this race condition. Which doesn't fix my issue because I'm still getting SIGSEGV.

panic: runtime error: invalid memory address or nil pointer dereference
[signal SIGSEGV: segmentation violation code=0x1 addr=0x10 pc=0x76e98e]

goroutine 19 [running]:
github.com/zmb3/spotify.(*Client).PlayOpt(0x0, 0xc4201a4090, 0x0, 0x0)
    /home/lumi/go/src/github.com/zmb3/spotify/player.go:286 +0x7e
main.mgmt(0x0, 0xc42007e180)
    /home/lumi/Projects/spotiman/mgmt.go:52 +0x160
created by main.main
    /home/lumi/Projects/spotiman/main.go:43 +0x109

the line in mgmt.go which causes the SIGSEGV is client.PlayOpt(&spotify.PlayOptions{PlaybackContext: &queue[0].URI})

jess-sch commented 6 years ago

I believe this issue is caused by this package. c.baseURL doesn't seem to be set, that's why we're getting a nil pointer dereference.

zmb3 commented 6 years ago

Sorry, I can't provide comprehensive troubleshooting with a limited view of the code. If you can create a small reproducible example I'd be happy to take a closer look.

jess-sch commented 6 years ago

Okay so this issue does indeed seem to be o my part... nvm.

jess-sch commented 6 years ago

Finally fixed it, seems to be that starting the goroutine that controls playback before the auth has completed results in a crash