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)
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)
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)
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)
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 )
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)}
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})
def test_valid(self): self.assertEqual(to_uri('track', 'b62'), 'spotify:track:b62')
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)