skip to Main Content

I’m creating a React (functional components) app which is based on Spotify Web API. I have no problems with this API but i have problems with Spotify Web Playback SDK. It’s a library which allows browser to control music player. As far as i know it’s the only way I can play Spotify music in my app. So i tried to implement it with their official tutorial. But it’s based on the singular object Player and it has methods such as setVolume, togglePlay or nextTrack. I want to be able to access this methods in different components. And my question is how to share this one object with it’s methods? I use redux toolkit for sharing data but i suppose I can’t share complex objects like this with it or can I?
I thought about creating Player object in a root and pass it with props but then I would have to useState for it in every component to show data from it and the object wouldn’t be in sync. Am I misunderstanding something or it’s a wrong way of doing it?
I don’t need this object overall. What I need is data from it and to be able to call it’s methods in components.

Here is a tutorial that I was following but it has no nested components.
Here is a reference to the Player object that I want to use.

UPDATE

I tried to solve it using Context API.

SpotifyPlayerContext.jsx

import React, { createContext, useState } from 'react'
import { useSelector } from 'react-redux'

export const SpotifyPlayerContext = createContext()

export const SpotifyPlayerProvider = ({ children }) => {
    const user = useSelector(state => state.user.value)
    const [spotifyPlayer, setSpotifyPlayer] = useState()

    const initSpotifyPlayer = () => {
        const script = document.createElement("script")
        script.src = "https://sdk.scdn.co/spotify-player.js"
        script.async = true
        document.body.appendChild(script)

        window.onSpotifyWebPlaybackSDKReady = () => {
            const playerInstance = new window.Spotify.Player({
                name: "Figure Out The Lyrics",
                getOAuthToken: (cb) => { cb(user.token) }
            })

            playerInstance.connect()
            
            playerInstance.addListener("ready", ({ device_id }) => {
                console.log("Device ID", device_id)
                playerInstance.device_id = device_id
                setSpotifyPlayer({...playerInstance})
            })
            playerInstance.addListener("player_state_changed", state => {
                console.log('player state changed', state);
                playerInstance.playback = state;
                setSpotifyPlayer({...playerInstance})
            })
        }
    }

    const value = { spotifyPlayer, setSpotifyPlayer, initSpotifyPlayer }

    return <SpotifyPlayerContext.Provider value={value}> {children} </SpotifyPlayerContext.Provider>
}

export default SpotifyPlayerContext

Player.jsx

import React, { useContext, useEffect, useState } from "react"
import { useDispatch, useSelector } from "react-redux"
import { Tooltip } from "react-tooltip"
import SpotifyPlayerContext from "../SpotifyPlayerContext"

const Player = ({ spotifyAPI }) => {
    const { spotifyPlayer, setSpotifyPlayer } = useContext(SpotifyPlayerContext)

    useEffect(() => {
        if (!spotifyPlayer) return
        if (!spotifyPlayer.playback) spotifyAPI.transferMyPlayback([spotifyPlayer.device_id])
        console.log("useEffect in Player", spotifyPlayer)
    }, [spotifyPlayer])

    return spotifyPlayer?.playback ? (
        <button onClick={() => spotifyPlayer.togglePlay()}>toggle</button>
    ) : (
        <div>loading</div>
    )
}

export default Player

I’m able to get data from it and when the spotifyPlayer changes the component is re-rendered but when i click the button I get error which says that spotifyPlayer.togglePlay is not a function
So, how am I supposed to access it’s methods? I guess that when I assign playerInstance to spotifyPlayer state it looses it’s methods but how to solve this problem?

2

Answers


  1. Chosen as BEST ANSWER

    Okay, I was able to make it work. Instead of creating playerInstance when the onSpotifyWebPlaybackSDKReady I created it even outside of the component and only assign the player to it. So now I get data from spotifyPlayer but I call methods on playerInstance. Maybe it's not perfect but it looks like it works for now.

    Updated SpotifyPlayerContext.jsx

    import React, { createContext, useEffect, useState } from "react"
    import { useSelector } from "react-redux"
    
    export const SpotifyPlayerContext = createContext()
    
    let playerInstance; // create variable
    
    export const SpotifyPlayerProvider = ({ children }) => {
        const user = useSelector(state => state.user.value)
        const [spotifyPlayer, setSpotifyPlayer] = useState()
    
        useEffect(() => {
            console.log(spotifyPlayer)
        }, [spotifyPlayer])
    
        const initSpotifyPlayer = () => {
            const script = document.createElement("script")
            script.src = "https://sdk.scdn.co/spotify-player.js"
            script.async = true
            document.body.appendChild(script)
    
            window.onSpotifyWebPlaybackSDKReady = () => { // only assign
                playerInstance = new window.Spotify.Player({
                    name: "Figure Out The Lyrics",
                    getOAuthToken: (cb) => {
                        cb(user.token)
                    },
                    volume: 0.1
                })
    
                playerInstance.connect()
    
                playerInstance.addListener("ready", ({ device_id }) => {
                    console.log("Device ID", device_id)
                    playerInstance.device_id = device_id
                    setSpotifyPlayer(playerInstance)
                })
                playerInstance.addListener("player_state_changed", (state) => {
                    console.log("player state changed", state)
                    playerInstance.playback = state
                    setSpotifyPlayer({ ...playerInstance })
                    console.log("instance", playerInstance)
                })
            }
        }
    
        const value = { spotifyPlayer, setSpotifyPlayer, initSpotifyPlayer, playerInstance } // export playerInstance
    
        return <SpotifyPlayerContext.Provider value={value}> {children} </SpotifyPlayerContext.Provider>
    }
    
    export default SpotifyPlayerContext
    

    Updated Player.jsx

    import React, { useContext, useEffect, useState } from "react"
    import { useDispatch, useSelector } from "react-redux"
    import { Tooltip } from "react-tooltip"
    import SpotifyPlayerContext from "../SpotifyPlayerContext"
    
    const Player = ({ spotifyAPI }) => {
        const { spotifyPlayer, setSpotifyPlayer, playerInstance } = useContext(SpotifyPlayerContext) // import playerInstance
    
        useEffect(() => {
            if (!spotifyPlayer) return
            if (!spotifyPlayer.playback) spotifyAPI.transferMyPlayback([spotifyPlayer.device_id])
            console.log("useEffect in Player", spotifyPlayer)
        }, [spotifyPlayer])
    
        return spotifyPlayer?.playback && playerInstance ? (
            <button onClick={() => playerInstance.togglePlay()}>toggle</button> // call it's methods
        ) : (
            <div>loading</div>
        )
    }
    
    export default Player
    

  2. There is this line in the tutorial:

    const player = new window.Spotify.Player(...)
    

    Instead of keeping this player instance in just one component, save it in a module that your other components can import:

    // player.js
    
    let player;
    
    export const savePlayerInstance = (playerInstance) => {
        player = playerInstance;
    };
    
    export const getPlayer = () => player;
    

    So you would import { savePlayerInstance } from 'some/path/player' and call setPlayer(player) after the instance is created (the line from your tutorial). Other modules can import { getPlayer } from 'some/path/player', call getPlayer() and invoke its methods.

    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search