Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Refactor PlayOrResumeOptions #204

Merged
merged 4 commits into from
Feb 20, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions lib/src/endpoints/player.dart
Original file line number Diff line number Diff line change
Expand Up @@ -126,7 +126,7 @@ class PlayerEndpoint extends _MeEndpointBase {
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: trackUris, positionMs: positionMs);
var options = StartWithUrisOptions(uris: trackUris, positionMs: positionMs);
return startOrResume(
deviceId: deviceId,
options: options,
Expand All @@ -148,7 +148,7 @@ class PlayerEndpoint extends _MeEndpointBase {
bool retrievePlaybackState = true}) async {
assert(
contextUri.isNotEmpty, 'Cannot start playback with empty context uri');
var options = StartOrResumeOptions(contextUri: contextUri, offset: offset);
var options = StartWithContextOptions(contextUri: contextUri, offset: offset);
return startOrResume(
deviceId: deviceId,
options: options,
Expand Down
11 changes: 8 additions & 3 deletions lib/src/models/_models.g.dart

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

38 changes: 27 additions & 11 deletions lib/src/models/player.dart
Original file line number Diff line number Diff line change
Expand Up @@ -117,14 +117,38 @@ class Actions extends Object {
bool? transferringPlayback;
}

abstract class StartOrResumeOptions extends Object {
Map<String, dynamic> toJson();
}

@JsonSerializable(createFactory: false)
class StartOrResumeOptions extends Object {
class StartWithContextOptions extends StartOrResumeOptions {

StartWithContextOptions({this.contextUri, this.offset});

/// Optional. Spotify URI of the context to play. Valid contexts are albums,
/// artists & playlists.
/// Example: "spotify:album:1Je1IMUlBXcx1Fz0WE7oPT"
@JsonKey(name: 'context_uri')
String? contextUri;

/// Optional. Indicates from where in the context playback should start.
/// Only available when [contextUri] corresponds to an album or playlist object
@JsonKey(toJson: _offsetToJson)
Offset? offset;

@override
Map<String, dynamic> toJson() => _$StartWithContextOptionsToJson(this);

static Map<String, dynamic>? _offsetToJson(Offset? offset) =>
offset?.toJson();
}

@JsonSerializable(createFactory: false)
class StartWithUrisOptions extends StartOrResumeOptions {

StartWithUrisOptions({this.uris, this.positionMs});

/// Optional. A JSON array of the Spotify track URIs to play.
///
/// Example:
Expand All @@ -136,21 +160,13 @@ class StartOrResumeOptions extends Object {
/// ```
List<String>? uris;

/// Optional. Indicates from where in the context playback should start.
/// Only available when [contextUri] corresponds to an album or playlist object
@JsonKey(toJson: _offsetToJson)
Offset? offset;

/// Optional. The position in milliseconds to start playback.
@JsonKey(name: 'position_ms')
int? positionMs;

StartOrResumeOptions(
{this.contextUri, this.uris, this.offset, this.positionMs});

Map<String, dynamic> toJson() => _$StartOrResumeOptionsToJson(this);
@override
Map<String, dynamic> toJson() => _$StartWithUrisOptionsToJson(this);

static Map<String, dynamic>? _offsetToJson(Offset? offset) => offset?.toJson();
}

abstract class Offset {
Expand Down
93 changes: 93 additions & 0 deletions test/data/v1/me/player/play.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
{
"device": {
"id": "123",
"is_active": true,
"is_private_session": false,
"is_restricted": false,
"name": "spotify-player",
"supports_volume": true,
"type": "Speaker",
"volume_percent": 50
},
"shuffle_state": false,
"smart_shuffle": false,
"repeat_state": "off",
"timestamp": 1708246313962,
"context": null,
"progress_ms": 96749,
"item": {
"album": {
"album_type": "album",
"artists": [
{
"external_urls": {
"spotify": "https://open.spotify.com/artist/0OcclcP5o8VKH2TRqSY2A7"
},
"href": "https://api.spotify.com/v1/artists/0OcclcP5o8VKH2TRqSY2A7",
"id": "0OcclcP5o8VKH2TRqSY2A7",
"name": "Howard Shore",
"type": "artist",
"uri": "spotify:artist:0OcclcP5o8VKH2TRqSY2A7"
}
],
"available_markets": ["AR"],
"external_urls": {
"spotify": "https://open.spotify.com/album/55RTkgUCP7t80hiTUhATMH"
},
"href": "https://api.spotify.com/v1/albums/55RTkgUCP7t80hiTUhATMH",
"id": "55RTkgUCP7t80hiTUhATMH",
"images": [
{
"height": 640,
"url": "https://i.scdn.co/image/ab67616d0000b2738236dee9524214e0e6be4a1f",
"width": 640
}
],
"name": "The Lord of the Rings: The Fellowship of the Ring - the Complete Recordings",
"release_date": "2001",
"release_date_precision": "year",
"total_tracks": 37,
"type": "album",
"uri": "spotify:album:55RTkgUCP7t80hiTUhATMH"
},
"artists": [
{
"external_urls": {
"spotify": "https://open.spotify.com/artist/0OcclcP5o8VKH2TRqSY2A7"
},
"href": "https://api.spotify.com/v1/artists/0OcclcP5o8VKH2TRqSY2A7",
"id": "0OcclcP5o8VKH2TRqSY2A7",
"name": "Howard Shore",
"type": "artist",
"uri": "spotify:artist:0OcclcP5o8VKH2TRqSY2A7"
}
],
"available_markets": ["AR"],
"disc_number": 1,
"duration_ms": 149480,
"explicit": false,
"external_ids": {
"isrc": "USRE10501559"
},
"external_urls": {
"spotify": "https://open.spotify.com/track/6zW80jVqLtgSF1yCtGHiiD"
},
"href": "https://api.spotify.com/v1/tracks/6zW80jVqLtgSF1yCtGHiiD",
"id": "6zW80jVqLtgSF1yCtGHiiD",
"is_local": false,
"name": "The Shire",
"popularity": 66,
"preview_url": "https://p.scdn.co/mp3-preview/0cb0d3080071ec5c911ea209ec4fc2258e2081d6?cid=a51c360ccea644af8e2a0b1baad8a878",
"track_number": 2,
"type": "track",
"uri": "spotify:track:6zW80jVqLtgSF1yCtGHiiD"
},
"currently_playing_type": "track",
"actions": {
"disallows": {
"skipping_prev": true,
"toggling_repeat_track": true
}
},
"is_playing": false
}
25 changes: 20 additions & 5 deletions test/spotify_mock.dart
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,9 @@ class SpotifyApiMock extends SpotifyApiBase {

set mockHttpErrors(Iterator<MockHttpError> errors) =>
(client as MockClient)._mockHttpErrors = errors;

set interceptor(Function(String method, String url, Map<String, String>? headers, [String? body])? interceptor) =>
(client as MockClient).interceptFunction = interceptor;
}

class MockClient implements oauth2.Client {
Expand All @@ -22,6 +25,14 @@ class MockClient implements oauth2.Client {
_mockHttpErrors = mockHttpErrors;
}

Function(String method, String url, Map<String, String>? headers, [String? body])? interceptFunction;

void _intercept(String method, String url, Map<String, String>? headers, [String? body]) {
if (interceptFunction != null) {
interceptFunction!(method, url, headers, body);
}
}

@override
String? identifier;

Expand Down Expand Up @@ -63,6 +74,7 @@ class MockClient implements oauth2.Client {

@override
Future<http.Response> get(url, {Map<String, String>? headers}) async {
_intercept('GET', url.toString(), headers);
final err = _getMockError();
if (err != null) {
return createErrorResponse(err);
Expand All @@ -71,8 +83,9 @@ class MockClient implements oauth2.Client {
}

@override
Future<http.Response> head(url, {Map<String, String>? headers}) {
throw 'Not implemented';
Future<http.Response> head(url, {Map<String, String>? headers}) async {
_intercept('HEAD', url.toString(), headers);
return createSuccessResponse();
}

@override
Expand All @@ -84,6 +97,7 @@ class MockClient implements oauth2.Client {
@override
Future<http.Response> post(url,
{Map<String, String>? headers, body, Encoding? encoding}) async {
_intercept('POST', url.toString(), headers, body.toString());
final err = _getMockError();
if (err != null) {
return createErrorResponse(err);
Expand All @@ -93,8 +107,9 @@ class MockClient implements oauth2.Client {

@override
Future<http.Response> put(url,
{Map<String, String>? headers, body, Encoding? encoding}) {
throw 'Not implemented';
{Map<String, String>? headers, body, Encoding? encoding}) async {
_intercept('PUT', url.toString(), headers, body.toString());
return createSuccessResponse(_readPath(url));
}

@override
Expand Down Expand Up @@ -126,7 +141,7 @@ class MockClient implements oauth2.Client {
throw 'Not implemented';
}

http.Response createSuccessResponse(String body) {
http.Response createSuccessResponse([String body = ""]) {
/// necessary due to using Latin-1 encoding per default.
/// https://stackoverflow.com/questions/52990816/dart-json-encodedata-can-not-accept-other-language
return http.Response(body, 200, headers: {
Expand Down
33 changes: 33 additions & 0 deletions test/spotify_test.dart
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import 'dart:async';
import 'dart:convert';
import 'spotify_mock.dart';
import 'package:test/test.dart';
import 'package:spotify/spotify.dart';
Expand All @@ -9,6 +10,10 @@ Future main() async {
'clientSecret',
));

tearDown(() {
spotify.interceptor = null;
});

group('Albums', () {
test('get', () async {
var album = await spotify.albums.get('4aawyAB9vmqN3uQ7FjRGTy');
Expand Down Expand Up @@ -480,6 +485,34 @@ Future main() async {
expect(result.actions?.resuming, false);
expect(result.actions?.pausing, true);
});


test('startWithContext', () async {
spotify.interceptor = (method, url, headers, [body]) {
// checking sincce startWithContext makes a PUT and a GET request
// to retrieve the current playbackstate
if (method == 'PUT') {
expect(method, 'PUT');
expect(body, isNotNull);
expect(body, '{"context_uri":"contextUri","offset":{"uri":"urioffset"}}');
}
};
await spotify.player.startWithContext('contextUri', offset: UriOffset('urioffset'));
});

test('startWithUris', () async {
spotify.interceptor = (method, url, headers, [body]) {
// checking sincce startWithTracks makes a PUT and a GET request
// to retrieve the current playbackstate
if (method == 'PUT') {
expect(method, 'PUT');
expect(body, isNotNull);
expect(body, '{"uris":["track1"],"position_ms":10}');
}
};
await spotify.player.startWithTracks(['track1'], positionMs: 10);
});

});

group('Tracks', () {
Expand Down