Esempio n. 1
0
    def playback_start_tracks(self,
                              track_ids: list,
                              offset: Union[int, str] = None,
                              position_ms: int = None,
                              device_id: str = None) -> None:
        """
        Start playback of one or more tracks.

        Requires the user-modify-playback-state scope.

        Parameters
        ----------
        track_ids
            track IDs to start playing
        offset
            offset into tracks by index or track ID
        position_ms
            initial position of first played track
        device_id
            device to start playback on
        """
        payload = {
            'uris': [to_uri('track', t) for t in track_ids],
            'offset': offset_to_dict(offset),
            'position_ms': position_ms,
        }
        payload = {k: v for k, v in payload.items() if v is not None}
        self._put('me/player/play', payload=payload, device_id=device_id)
Esempio n. 2
0
    def playlist_tracks_remove_occurrences(self,
                                           playlist_id: str,
                                           track_refs: List[Tuple[str, int]],
                                           snapshot_id: str = None) -> str:
        """
        Remove tracks from a playlist by track ID and position.

        Requires the playlist-modify-public scope. To modify private playlists
        the playlist-modify-private scope is required.

        Parameters
        ----------
        playlist_id
            playlist ID
        track_refs
            a list of tuples containing the ID and index of tracks to remove
        snapshot_id
            snapshot ID for the playlist

        Returns
        -------
        str
            snapshot ID for the playlist
        """
        gathered = {}
        for id_, ix in track_refs:
            gathered.setdefault(id_, []).append(ix)

        tracks = [{
            'uri': to_uri('track', id_),
            'positions': ix_list
        } for id_, ix_list in gathered.items()]
        return self._generic_playlist_tracks_remove(playlist_id,
                                                    {'tracks': tracks},
                                                    snapshot_id)
Esempio n. 3
0
    def playlist_tracks_add(self,
                            playlist_id: str,
                            track_ids: list,
                            position: int = None) -> str:
        """
        Add tracks to a playlist.

        Requires the playlist-modify-public scope. To modify private playlists
        the playlist-modify-private scope is required.

        Parameters
        ----------
        playlist_id
            playlist ID
        track_ids
            list of track IDs, max 100 without chunking
        position
            position to add the tracks

        Returns
        -------
        str
            snapshot ID for the playlist
        """
        payload = {'uris': [to_uri('track', t) for t in track_ids]}
        return self._post(f'playlists/{playlist_id}/tracks',
                          payload=payload,
                          position=position)
Esempio n. 4
0
    def playlist_tracks_remove(self,
                               playlist_id: str,
                               track_ids: list,
                               snapshot_id: str = None) -> str:
        """
        Remove all occurrences of tracks from a playlist.

        Requires the playlist-modify-public scope. To modify private playlists
        the playlist-modify-private scope is required.

        Note that when chunked, ``snapshot_id`` is not updated between requests.

        Parameters
        ----------
        playlist_id
            playlist ID
        track_ids
            list of track IDs, max 100 without chunking
        snapshot_id
            snapshot ID for the playlist

        Returns
        -------
        str
            snapshot ID for the playlist
        """
        tracks = [{'uri': to_uri('track', t)} for t in track_ids]
        return self._generic_playlist_tracks_remove(playlist_id,
                                                    {'tracks': tracks},
                                                    snapshot_id)
Esempio n. 5
0
    def playlist_tracks_remove(
            self,
            playlist_id: str,
            track_ids: list,
            snapshot_id: str = None
    ) -> str:
        """
        Remove all occurrences of tracks from a playlist.

        Requires the playlist-modify-public scope. To modify private playlists
        the playlist-modify-private scope is required.

        Parameters
        ----------
        playlist_id
            playlist ID
        track_ids
            list of track IDs
        snapshot_id
            snapshot ID for the playlist

        Returns
        -------
        str
            snapshot ID for the playlist
        """
        tracks = [{'uri': to_uri('track', t)} for t in track_ids]
        return self._generic_playlist_tracks_remove(
            playlist_id,
            {'tracks': tracks},
            snapshot_id
        )
Esempio n. 6
0
def offset_to_dict(offset: Union[int, str]):
    """
    Parse playback start offset to an appropriate payload member.

    If offset is an integer, it is an index to a track position.
    If it is a string, it is a URI of a specific track.
    """
    if isinstance(offset, int):
        return {'position': offset}
    elif isinstance(offset, str):
        return {'uri': to_uri('track', offset)}
Esempio n. 7
0
    def playlist_tracks_replace(self, playlist_id: str, track_ids: list) -> None:
        """
        Replace all tracks in a playlist.

        Requires the playlist-modify-public scope. To modify private playlists
        the playlist-modify-private scope is required.

        Parameters
        ----------
        playlist_id
            playlist ID
        track_ids
            list of track IDs to add to the playlist
        """
        track_uris = [to_uri('track', t) for t in track_ids]
        self._put(f'playlists/{playlist_id}/tracks', payload={'uris': track_uris})
Esempio n. 8
0
 def test_valid(self):
     self.assertEqual(to_uri('track', 'b62'), 'spotify:track:b62')
Esempio n. 9
0
    def test_player(self):
        with self.subTest('Set volume'):
            self.client.playback_volume(0, device_id=self.device.id)

        with self.subTest('Transfer playback'):
            self.client.playback_transfer(self.device.id, force_play=True)

        self.client.playback_start_tracks(track_ids, offset=1)
        self.assertPlaying('Playback start with offset index', track_ids[1])

        playing = self.client.playback_currently_playing()
        with self.subTest('Currently playing has item'):
            self.assertIsNotNone(playing.item)

        self.client.playback_start_tracks(track_ids, offset=track_ids[1])
        self.assertPlaying('Playback start with offset uri', track_ids[1])

        self.client.playback_start_tracks(track_ids)
        self.assertPlaying('Playback start', track_ids[0])

        self.client.playback_pause()
        playing = self.currently_playing()
        with self.subTest('Playback pause'):
            self.assertFalse(playing.is_playing)

        with self.subTest('Player error: already paused'):
            with self.assertRaises(HTTPError):
                self.client.playback_pause()

        self.client.playback_resume()
        playing = self.currently_playing()
        with self.subTest('Playback resume'):
            self.assertTrue(playing.is_playing)

        self.client.playback_next()
        self.assertPlaying('Playback next', track_ids[1])

        self.client.playback_previous()
        self.assertPlaying('Playback previous', track_ids[0])

        self.client.playback_seek(30 * 1000)
        playing = self.currently_playing()
        with self.subTest('Playback seek'):
            self.assertGreater(playing.progress_ms, 30 * 1000)

        with self.subTest('Playback repeat'):
            self.client.playback_repeat('off')

        with self.subTest('Playback shuffle'):
            self.client.playback_shuffle(False)

        with self.subTest('Playback start context'):
            self.client.playback_start_context(to_uri('album', album_id))

        self.client.playback_queue_add(to_uri('track', track_ids[0]))
        self.client.playback_next()
        self.assertPlaying('Queue consumed on next', track_ids[0])

        with self.subTest('Add episode to queue'):
            self.client.playback_queue_add(to_uri('episode', episode_id))

        self.client.playback_next()
        playing = self.currently_playing()
        with self.subTest('Currently playing episode returned by default'):
            self.assertEqual(playing.item.id, episode_id)

        playing = self.client.playback_currently_playing(tracks_only=True)
        with self.subTest('Currently playing episode is none if only tracks'):
            self.assertIsNone(playing.item)

        playing = self.client.playback()
        with self.subTest('Playback episode returned by default'):
            self.assertEqual(playing.item.id, episode_id)

        playing = self.client.playback(tracks_only=True)
        with self.subTest('Playback episode is none if only tracks'):
            self.assertIsNone(playing.item)