From 582ed0d5611389f7ed407ab66c0506b445c7bf12 Mon Sep 17 00:00:00 2001 From: Hayri Bakici Date: Mon, 27 Nov 2023 18:01:39 +0100 Subject: [PATCH 1/4] first attempt to refactor `startOrResume` --- lib/src/endpoints/player.dart | 63 ++++++++++++++++++++++++++--------- lib/src/models/_models.g.dart | 4 +-- lib/src/models/player.dart | 15 +++++---- 3 files changed, 57 insertions(+), 25 deletions(-) diff --git a/lib/src/endpoints/player.dart b/lib/src/endpoints/player.dart index df4faf8..1dfb4f7 100644 --- a/lib/src/endpoints/player.dart +++ b/lib/src/endpoints/player.dart @@ -18,8 +18,7 @@ class PlayerEndpoint extends _MeEndpointBase { /// will be retrieved after setting the volume. Defaults to true. Future shuffle(bool state, {String? deviceId, bool retrievePlaybackState = true}) async { - await _api._put('$_path/shuffle?' + - _buildQuery({'state': state, 'deviceId': deviceId})); + await _api._put('$_path/shuffle?${_buildQuery({'state': state, 'deviceId': deviceId})}'); return retrievePlaybackState ? playbackState() : null; } @@ -34,8 +33,8 @@ class PlayerEndpoint extends _MeEndpointBase { /// Returns the current playback state, including progress, track /// and active device. Future playbackState([Market? market]) async { - var jsonString = await _api - ._get('$_path?' + _buildQuery({'market': market?.name})); + var jsonString = + await _api._get('$_path?${_buildQuery({'market': market?.name})}'); final map = json.decode(jsonString); return PlaybackState.fromJson(map); } @@ -87,6 +86,7 @@ class PlayerEndpoint extends _MeEndpointBase { /// from the context's current track. /// [retrievePlaybackState] is optional. If true, the current playback state /// will be retrieved. Defaults to true. + @Deprecated("Use `startOrResumeTracks` or `startOrResumeContext` instead") Future startOrResume( {String? deviceId, StartOrResumeOptions? options, @@ -94,12 +94,46 @@ class PlayerEndpoint extends _MeEndpointBase { var body = options?.toJson(); var json = jsonEncode(body ?? ''); - await _api._put( - '$_path/play?' + _buildQuery({'device_id': deviceId}), json); + await _api._put('$_path/play?${_buildQuery({'device_id': deviceId})}', json); return retrievePlaybackState ? playbackState() : null; } + /// Start a new context with given [uris] or resume current playback on the + /// user's active device. + /// [deviceId] is optional. If not provided, the user's currently active device + /// is the target. + /// [uris] is optional. If not provided, playback will start + /// from the context's current track. + /// [retrievePlaybackState] is optional. If `true`, the current [PlaybackState] + /// will be retrieved. Defaults to `true`. + Future startOrResumeTracks( + {String? deviceId, + List uris = const [], + int positionMs = 0, + bool retrievePlaybackState = true}) async { + assert(uris.isNotEmpty, "No uris provided to start of resume playback"); + + var options = StartOrResumeOptions(uris: uris, positionMs: positionMs); + return startOrResume( + deviceId: deviceId, + options: options, + retrievePlaybackState: retrievePlaybackState); + } + + Future startOrResumeContext( + {String? deviceId, + String? contextUri, + Offset? offset, + bool retrievePlaybackState = true}) async { + + var options = StartOrResumeOptions(contextUri: contextUri, offset: offset); + return startOrResume( + deviceId: deviceId, + options: options, + retrievePlaybackState: retrievePlaybackState); + } + /// Pause playback on the user's account. /// [deviceId] is optional. If not provided, the user's currently active device /// is the target. @@ -107,7 +141,7 @@ class PlayerEndpoint extends _MeEndpointBase { /// will be retrieved. Defaults to true. Future pause( {String? deviceId, bool retrievePlaybackState = true}) async { - await _api._put('$_path/pause?' + _buildQuery({'device_id': deviceId})); + await _api._put('$_path/pause?${_buildQuery({'device_id': deviceId})}'); return retrievePlaybackState ? playbackState() : null; } @@ -119,7 +153,7 @@ class PlayerEndpoint extends _MeEndpointBase { /// will be retrieved. Defaults to true. Future previous( {String? deviceId, bool retrievePlaybackState = true}) async { - await _api._post('$_path/previous?' + _buildQuery({'device_id': deviceId})); + await _api._post('$_path/previous?${_buildQuery({'device_id': deviceId})}'); return retrievePlaybackState ? playbackState() : null; } @@ -131,7 +165,7 @@ class PlayerEndpoint extends _MeEndpointBase { /// will be retrieved. Defaults to true. Future next( {String? deviceId, bool retrievePlaybackState = true}) async { - await _api._post('$_path/next?' + _buildQuery({'device_id': deviceId})); + await _api._post('$_path/next?${_buildQuery({'device_id': deviceId})}'); return retrievePlaybackState ? playbackState() : null; } @@ -145,8 +179,7 @@ class PlayerEndpoint extends _MeEndpointBase { Future seek(int positionMs, {String? deviceId, bool retrievePlaybackState = true}) async { assert(positionMs >= 0, 'positionMs must be greater or equal to 0'); - await _api._put('$_path/seek?' + - _buildQuery({'position_ms': positionMs, 'device_id': deviceId})); + await _api._put('$_path/seek?${_buildQuery({'position_ms': positionMs, 'device_id': deviceId})}'); return retrievePlaybackState ? playbackState() : null; } @@ -160,11 +193,10 @@ class PlayerEndpoint extends _MeEndpointBase { /// will be retrieved. Defaults to true. Future repeat(RepeatState state, {String? deviceId, bool retrievePlaybackState = true}) async { - await _api._put('$_path/repeat?' + - _buildQuery({ + await _api._put('$_path/repeat?${_buildQuery({ 'state': state.toString().split('.').last, 'device_id': deviceId - })); + })}'); return retrievePlaybackState ? playbackState() : null; } @@ -180,8 +212,7 @@ class PlayerEndpoint extends _MeEndpointBase { {String? deviceId, bool retrievePlaybackState = true}) async { assert(volumePercent >= 0 && volumePercent <= 100, 'Volume must be between 0 and 100'); - await _api._put('$_path/volume?' + - _buildQuery({'volume_percent': volumePercent, 'device_id': deviceId})); + await _api._put('$_path/volume?${_buildQuery({'volume_percent': volumePercent, 'device_id': deviceId})}'); return retrievePlaybackState ? playbackState() : null; } diff --git a/lib/src/models/_models.g.dart b/lib/src/models/_models.g.dart index f9858ca..83ddc81 100644 --- a/lib/src/models/_models.g.dart +++ b/lib/src/models/_models.g.dart @@ -546,7 +546,7 @@ PlaybackState _$PlaybackStateFromJson(Map json) => ..context = json['context'] == null ? null : PlayerContext.fromJson(json['context'] as Map) - ..progress_ms = json['progress_ms'] as int? + ..progressMs = json['progress_ms'] as int? ..item = json['item'] == null ? null : Track.fromJson(json['item'] as Map) @@ -576,7 +576,7 @@ const _$RepeatStateEnumMap = { PlayerContext _$PlayerContextFromJson(Map json) => PlayerContext() - ..external_urls = json['external_urls'] == null + ..externalUrls = json['external_urls'] == null ? null : ExternalUrls.fromJson(json['external_urls'] as Map) ..href = json['href'] as String? diff --git a/lib/src/models/player.dart b/lib/src/models/player.dart index 24b0ebc..7da61d5 100644 --- a/lib/src/models/player.dart +++ b/lib/src/models/player.dart @@ -18,7 +18,8 @@ class PlaybackState extends Object { PlayerContext? context; /// Progress into the currently playing track. Can be `null`. - int? progress_ms; + @JsonKey(name: 'progress_ms') + int? progressMs; /// The currently playing track. Can be `null`. Track? item; @@ -40,7 +41,7 @@ class PlaybackState extends Object { @JsonKey(name: 'shuffle_state', defaultValue: false) bool? isShuffling; - /// The repeat state. Can be [RepeatState.off], [RepeatState.track] or + /// The repeat state. Can be [RepeatState.off], [RepeatState.track] or /// [RepeatState.context] @JsonKey(name: 'repeat_state', defaultValue: RepeatState.off) RepeatState? repeatState; @@ -55,7 +56,8 @@ class PlayerContext extends Object { _$PlayerContextFromJson(json); /// The external_urls of the context, or `null` if not available. - ExternalUrls? external_urls; + @JsonKey(name: 'external_urls') + ExternalUrls? externalUrls; /// The href of the context, or `null` if not available. String? href; @@ -125,7 +127,7 @@ class StartOrResumeOptions extends Object { /// Optional. A JSON array of the Spotify track URIs to play. /// - /// Example: + /// Example: /// ```json /// [ /// "spotify:track:4iV5W9uYEdYUVa79Axb7Rh", @@ -148,9 +150,8 @@ class StartOrResumeOptions extends Object { Map toJson() => _$StartOrResumeOptionsToJson(this); - static Map _offsetToJson(Offset? offset) { - return offset?.toJson() ?? {}; - } + static Map _offsetToJson(Offset? offset) => + offset?.toJson() ?? {}; } abstract class Offset { From 4a286f1cd5f3915bc7b4979e0ce86567bf7816d6 Mon Sep 17 00:00:00 2001 From: Hayri Bakici Date: Wed, 29 Nov 2023 10:28:37 +0100 Subject: [PATCH 2/4] [wip] refactor startOrResume with splitting - needs more documentation --- lib/src/endpoints/player.dart | 57 ++++++++++++++++++++++++++--------- 1 file changed, 42 insertions(+), 15 deletions(-) diff --git a/lib/src/endpoints/player.dart b/lib/src/endpoints/player.dart index 1dfb4f7..803ee2c 100644 --- a/lib/src/endpoints/player.dart +++ b/lib/src/endpoints/player.dart @@ -18,7 +18,10 @@ class PlayerEndpoint extends _MeEndpointBase { /// will be retrieved after setting the volume. Defaults to true. Future shuffle(bool state, {String? deviceId, bool retrievePlaybackState = true}) async { - await _api._put('$_path/shuffle?${_buildQuery({'state': state, 'deviceId': deviceId})}'); + await _api._put('$_path/shuffle?${_buildQuery({ + 'state': state, + 'deviceId': deviceId + })}'); return retrievePlaybackState ? playbackState() : null; } @@ -86,7 +89,7 @@ class PlayerEndpoint extends _MeEndpointBase { /// from the context's current track. /// [retrievePlaybackState] is optional. If true, the current playback state /// will be retrieved. Defaults to true. - @Deprecated("Use `startOrResumeTracks` or `startOrResumeContext` instead") + @Deprecated("Use `startWithTracks` or `startWithContext` instead") Future startOrResume( {String? deviceId, StartOrResumeOptions? options, @@ -94,39 +97,48 @@ class PlayerEndpoint extends _MeEndpointBase { var body = options?.toJson(); var json = jsonEncode(body ?? ''); - await _api._put('$_path/play?${_buildQuery({'device_id': deviceId})}', json); + await _api._put( + '$_path/play?${_buildQuery({'device_id': deviceId})}', json); return retrievePlaybackState ? playbackState() : null; } - /// Start a new context with given [uris] or resume current playback on the + /// Start a new playback context with given [trackUris] or resume current playback on the /// user's active device. /// [deviceId] is optional. If not provided, the user's currently active device /// is the target. - /// [uris] is optional. If not provided, playback will start + /// [trackUris] is optional. If not provided, playback will start /// from the context's current track. /// [retrievePlaybackState] is optional. If `true`, the current [PlaybackState] - /// will be retrieved. Defaults to `true`. - Future startOrResumeTracks( + /// will be retrieved. Default's to `true`. + Future startWithTracks( {String? deviceId, - List uris = const [], + List trackUris = const [], int positionMs = 0, bool retrievePlaybackState = true}) async { - assert(uris.isNotEmpty, "No uris provided to start of resume playback"); + assert(trackUris.isNotEmpty, 'Cannot start playback with empty track uris'); + assert(positionMs >= 0, 'Position must be greater than or equal to 0'); - var options = StartOrResumeOptions(uris: uris, positionMs: positionMs); + var options = StartOrResumeOptions(uris: trackUris, positionMs: positionMs); return startOrResume( deviceId: deviceId, options: options, retrievePlaybackState: retrievePlaybackState); } - Future startOrResumeContext( + /// Start a new playback context (album, playlist) with given a [contextUri]. + /// [deviceId] is optional. If not provided, the user's currently active device + /// is the target. + /// [contextUri] is optional. If not provided, playback will start + /// from the context's current track. + /// [retrievePlaybackState] is optional. If `true`, the current [PlaybackState] + /// will be retrieved. Default's to `true`. + Future startWithContext( {String? deviceId, String? contextUri, Offset? offset, bool retrievePlaybackState = true}) async { - + assert(contextUri?.isNotEmpty ?? false, 'Cannot start playback with empty context uri'); var options = StartOrResumeOptions(contextUri: contextUri, offset: offset); return startOrResume( deviceId: deviceId, @@ -134,6 +146,15 @@ class PlayerEndpoint extends _MeEndpointBase { retrievePlaybackState: retrievePlaybackState); } + /// Resume current playback on the user's active device if not specifically + /// set with [deviceId]. + /// [retrievePlaybackState] is optional. If `true`, the current [PlaybackState] + /// will be retrieved. Default's to `true`. + Future resume( + {String? deviceId, bool retrievePlaybackState = true}) async => + startOrResume( + deviceId: deviceId, retrievePlaybackState: retrievePlaybackState); + /// Pause playback on the user's account. /// [deviceId] is optional. If not provided, the user's currently active device /// is the target. @@ -179,7 +200,10 @@ class PlayerEndpoint extends _MeEndpointBase { Future seek(int positionMs, {String? deviceId, bool retrievePlaybackState = true}) async { assert(positionMs >= 0, 'positionMs must be greater or equal to 0'); - await _api._put('$_path/seek?${_buildQuery({'position_ms': positionMs, 'device_id': deviceId})}'); + await _api._put('$_path/seek?${_buildQuery({ + 'position_ms': positionMs, + 'device_id': deviceId + })}'); return retrievePlaybackState ? playbackState() : null; } @@ -212,12 +236,15 @@ class PlayerEndpoint extends _MeEndpointBase { {String? deviceId, bool retrievePlaybackState = true}) async { assert(volumePercent >= 0 && volumePercent <= 100, 'Volume must be between 0 and 100'); - await _api._put('$_path/volume?${_buildQuery({'volume_percent': volumePercent, 'device_id': deviceId})}'); + await _api._put('$_path/volume?${_buildQuery({ + 'volume_percent': volumePercent, + 'device_id': deviceId + })}'); return retrievePlaybackState ? playbackState() : null; } - /// Transfer playback to a new device and determine if + /// Transfer playback to a new device and determine if /// it should start [play]ing. Default is `true`. /// /// The `AuthorizationScope.connect.modifyPlaybackState` needs to be set. From 03cf3c9e8e32eca387b87385ead585cad71af71d Mon Sep 17 00:00:00 2001 From: Hayri Bakici Date: Fri, 1 Dec 2023 13:29:34 +0100 Subject: [PATCH 3/4] adds more documentation --- lib/src/endpoints/player.dart | 34 ++++++++++++++++++---------------- 1 file changed, 18 insertions(+), 16 deletions(-) diff --git a/lib/src/endpoints/player.dart b/lib/src/endpoints/player.dart index 803ee2c..6b4c922 100644 --- a/lib/src/endpoints/player.dart +++ b/lib/src/endpoints/player.dart @@ -89,7 +89,8 @@ class PlayerEndpoint extends _MeEndpointBase { /// from the context's current track. /// [retrievePlaybackState] is optional. If true, the current playback state /// will be retrieved. Defaults to true. - @Deprecated("Use `startWithTracks` or `startWithContext` instead") + @Deprecated( + "Use `startWithTracks()` or `startWithContext()` to start a new context and resume() instead") Future startOrResume( {String? deviceId, StartOrResumeOptions? options, @@ -103,17 +104,17 @@ class PlayerEndpoint extends _MeEndpointBase { return retrievePlaybackState ? playbackState() : null; } - /// Start a new playback context with given [trackUris] or resume current playback on the - /// user's active device. - /// [deviceId] is optional. If not provided, the user's currently active device - /// is the target. - /// [trackUris] is optional. If not provided, playback will start - /// from the context's current track. + /// Start a new playback context with given [trackUris] and with given optional + /// [deviceId]. If not provided, the user's currently active device + /// is the target. Playback can also start at [positionMs], which is set to `0` + /// by default. /// [retrievePlaybackState] is optional. If `true`, the current [PlaybackState] /// will be retrieved. Default's to `true`. - Future startWithTracks( + /// + /// Note: Before starting a new playback context check the [playbackState] + /// if necessary before [resume]ing. + Future startWithTracks(List trackUris, {String? deviceId, - List trackUris = const [], int positionMs = 0, bool retrievePlaybackState = true}) async { assert(trackUris.isNotEmpty, 'Cannot start playback with empty track uris'); @@ -127,18 +128,19 @@ class PlayerEndpoint extends _MeEndpointBase { } /// Start a new playback context (album, playlist) with given a [contextUri]. - /// [deviceId] is optional. If not provided, the user's currently active device - /// is the target. - /// [contextUri] is optional. If not provided, playback will start - /// from the context's current track. + /// and given optional [deviceId]. If not provided, the user's currently active + /// device is the target. Set [Offset] to start playback at a specific point. /// [retrievePlaybackState] is optional. If `true`, the current [PlaybackState] /// will be retrieved. Default's to `true`. - Future startWithContext( + /// + /// Note: Before starting a new playback context check the [playbackState] + /// if necessary before [resume]ing. + Future startWithContext(String contextUri, {String? deviceId, - String? contextUri, Offset? offset, bool retrievePlaybackState = true}) async { - assert(contextUri?.isNotEmpty ?? false, 'Cannot start playback with empty context uri'); + assert( + contextUri.isNotEmpty, 'Cannot start playback with empty context uri'); var options = StartOrResumeOptions(contextUri: contextUri, offset: offset); return startOrResume( deviceId: deviceId, From d925704a050f30d964be7a42cfa93798fb552e89 Mon Sep 17 00:00:00 2001 From: Hayri Bakici Date: Fri, 1 Dec 2023 13:30:55 +0100 Subject: [PATCH 4/4] adds more documentation --- lib/src/endpoints/player.dart | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/lib/src/endpoints/player.dart b/lib/src/endpoints/player.dart index 6b4c922..662208c 100644 --- a/lib/src/endpoints/player.dart +++ b/lib/src/endpoints/player.dart @@ -112,7 +112,8 @@ class PlayerEndpoint extends _MeEndpointBase { /// will be retrieved. Default's to `true`. /// /// Note: Before starting a new playback context check the [playbackState] - /// if necessary before [resume]ing. + /// if necessary before [resume]ing, otherwise you overwrite the current + /// context. Future startWithTracks(List trackUris, {String? deviceId, int positionMs = 0, @@ -134,7 +135,8 @@ class PlayerEndpoint extends _MeEndpointBase { /// will be retrieved. Default's to `true`. /// /// Note: Before starting a new playback context check the [playbackState] - /// if necessary before [resume]ing. + /// if necessary before [resume]ing, otherwise you overwrite the current + /// context. Future startWithContext(String contextUri, {String? deviceId, Offset? offset,