Skip to content

Commit 4f8f3bc

Browse files
committed
implement fallback playback accessor
1 parent 4d45166 commit 4f8f3bc

File tree

11 files changed

+148
-106
lines changed

11 files changed

+148
-106
lines changed

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -99,7 +99,7 @@ api.initialize();
9999

100100
Fetch an image of the current playing track:
101101
```java
102-
// Create an instance of the Open Spotify API
102+
// Create an instance of the Open Spotify API (Or use SpotifyAPI#getOpenAPI)
103103
OpenSpotifyAPI openSpotifyAPI = new OpenSpotifyAPI();
104104

105105
// Download the cover art of the current song

build.gradle

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ plugins {
44
}
55

66
group 'de.labystudio'
7-
version '1.1.13'
7+
version '1.1.14'
88

99
compileJava {
1010
sourceCompatibility = '1.8'

src/main/java/de/labystudio/spotifyapi/SpotifyAPI.java

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
import de.labystudio.spotifyapi.config.SpotifyConfiguration;
44
import de.labystudio.spotifyapi.model.MediaKey;
55
import de.labystudio.spotifyapi.model.Track;
6+
import de.labystudio.spotifyapi.open.OpenSpotifyAPI;
67

78
import java.util.concurrent.CompletableFuture;
89

@@ -147,6 +148,8 @@ default boolean hasTrack() {
147148
*/
148149
SpotifyConfiguration getConfiguration();
149150

151+
OpenSpotifyAPI getOpenAPI();
152+
150153
/**
151154
* Disconnect from the Spotify application and stop all background tasks.
152155
*/

src/main/java/de/labystudio/spotifyapi/open/OpenSpotifyAPI.java

Lines changed: 8 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -39,10 +39,6 @@ public class OpenSpotifyAPI {
3939

4040
private AccessTokenResponse accessTokenResponse;
4141

42-
public OpenSpotifyAPI() {
43-
this.generateAccessTokenAsync(accessTokenResponse -> this.accessTokenResponse = accessTokenResponse);
44-
}
45-
4642
/**
4743
* Generate an access token asynchronously for the open spotify api
4844
*/
@@ -262,13 +258,19 @@ public OpenTrack requestOpenTrack(String trackId) throws IOException {
262258
* @throws IOException if the request failed
263259
*/
264260
public <T> T request(String url, Class<?> clazz, boolean canGenerateNewAccessToken) throws IOException {
261+
// Generate access token if not present
262+
if (this.accessTokenResponse == null) {
263+
this.accessTokenResponse = this.generateAccessToken();
264+
}
265+
265266
// Connect
266267
HttpsURLConnection connection = (HttpsURLConnection) new URL(url).openConnection();
267268
connection.addRequestProperty("User-Agent", USER_AGENT);
268269
connection.addRequestProperty("referer", "https://open.spotify.com/");
269270
connection.addRequestProperty("app-platform", "WebPlayer");
270271
connection.addRequestProperty("origin", "https://open.spotify.com");
271272

273+
// Add access token
272274
if (this.accessTokenResponse != null) {
273275
connection.addRequestProperty("authorization", "Bearer " + this.accessTokenResponse.accessToken);
274276
}
@@ -290,8 +292,8 @@ public <T> T request(String url, Class<?> clazz, boolean canGenerateNewAccessTok
290292

291293
// Read response
292294
JsonReader reader = new JsonReader(new InputStreamReader(
293-
connection.getInputStream(),
294-
StandardCharsets.UTF_8
295+
connection.getInputStream(),
296+
StandardCharsets.UTF_8
295297
));
296298

297299
return GSON.fromJson(reader, clazz);

src/main/java/de/labystudio/spotifyapi/platform/AbstractTickSpotifyAPI.java

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
import de.labystudio.spotifyapi.SpotifyAPI;
44
import de.labystudio.spotifyapi.SpotifyListener;
55
import de.labystudio.spotifyapi.config.SpotifyConfiguration;
6+
import de.labystudio.spotifyapi.open.OpenSpotifyAPI;
67

78
import java.util.ArrayList;
89
import java.util.List;
@@ -22,6 +23,8 @@ public abstract class AbstractTickSpotifyAPI implements SpotifyAPI {
2223
*/
2324
protected final List<SpotifyListener> listeners = new ArrayList<>();
2425

26+
private OpenSpotifyAPI openAPI;
27+
2528
private SpotifyConfiguration configuration;
2629

2730
private ScheduledFuture<?> task;
@@ -100,6 +103,14 @@ public SpotifyConfiguration getConfiguration() {
100103
return this.configuration;
101104
}
102105

106+
@Override
107+
public final OpenSpotifyAPI getOpenAPI() {
108+
if (this.openAPI == null) {
109+
this.openAPI = new OpenSpotifyAPI();
110+
}
111+
return this.openAPI;
112+
}
113+
103114
@Override
104115
public void stop() {
105116
synchronized (this) {

src/main/java/de/labystudio/spotifyapi/platform/windows/WinSpotifyAPI.java

Lines changed: 21 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
import de.labystudio.spotifyapi.SpotifyListener;
44
import de.labystudio.spotifyapi.model.MediaKey;
55
import de.labystudio.spotifyapi.model.Track;
6+
import de.labystudio.spotifyapi.open.model.track.OpenTrack;
67
import de.labystudio.spotifyapi.platform.AbstractTickSpotifyAPI;
78
import de.labystudio.spotifyapi.platform.windows.api.WinApi;
89
import de.labystudio.spotifyapi.platform.windows.api.playback.PlaybackAccessor;
@@ -31,7 +32,6 @@ public class WinSpotifyAPI extends AbstractTickSpotifyAPI {
3132
private long lastAccessorPosition = -1;
3233
private long lastTimeSynced;
3334

34-
3535
/**
3636
* Updates the current track, position and playback state.
3737
* If the process is not connected, it will try to connect to the Spotify process.
@@ -49,7 +49,7 @@ protected void onTick() throws Exception {
4949
String trackId = this.process.getTrackId();
5050

5151
// Update playback status and check if it is valid
52-
if (!playback.update() || !this.process.isTrackIdValid(trackId)) {
52+
if (!this.process.isTrackIdValid(trackId) || !playback.update()) {
5353
this.positionKnown = false;
5454
this.currentPosition = -1;
5555
throw new IllegalStateException("Could not update playback");
@@ -69,6 +69,18 @@ protected void onTick() throws Exception {
6969
int trackLength = playback.getLength();
7070
boolean isFirstTrack = !this.hasTrack();
7171

72+
// Request track length from open API if not available
73+
if (!playback.hasTrackLength()) {
74+
try {
75+
OpenTrack openTrack = this.getOpenAPI().requestOpenTrack(trackId);
76+
if (openTrack != null) {
77+
trackLength = openTrack.durationMs;
78+
}
79+
} catch (Exception e) {
80+
e.printStackTrace();
81+
}
82+
}
83+
7284
Track track = new Track(trackId, title.getTrackName(), title.getTrackArtist(), trackLength);
7385
this.currentTrack = track;
7486

@@ -87,13 +99,19 @@ protected void onTick() throws Exception {
8799
if (isPlaying != this.isPlaying) {
88100
this.isPlaying = isPlaying;
89101

102+
// Forget position if the song is paused and the position is unknown
103+
if (!playback.hasTrackPosition()) {
104+
this.positionKnown = false;
105+
this.currentPosition = -1;
106+
}
107+
90108
// Fire on play back changed
91109
this.listeners.forEach(listener -> listener.onPlayBackChanged(isPlaying));
92110
}
93111

94112
// Handle position changes
95113
int position = playback.getPosition();
96-
if (position != this.lastAccessorPosition) {
114+
if (playback.hasTrackPosition() && position != this.lastAccessorPosition) {
97115
this.lastAccessorPosition = position;
98116
this.updatePosition(position, false);
99117
}

src/main/java/de/labystudio/spotifyapi/platform/windows/api/WinProcess.java

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -308,6 +308,26 @@ public long findAddressOfText(long start, long end, String text, SearchCondition
308308
return this.findInMemory(start, end, text.getBytes(), condition);
309309
}
310310

311+
/**
312+
* Find the address of multiple text strings inside the memory.
313+
* If there are multiple matches of the text, the given index will be used to select the correct one.
314+
*
315+
* @param start The address to start searching from.
316+
* @param end The address to stop searching at.
317+
* @param condition The condition function to call for each matching address.
318+
* @param texts The texts to search for.
319+
* @return The address of the text at the given index
320+
*/
321+
public long findAddressOfTexts(long start, long end, SearchCondition condition, String... texts) {
322+
for (String text : texts) {
323+
long address = this.findAddressOfText(start, end, text, condition);
324+
if (address != -1) {
325+
return address;
326+
}
327+
}
328+
return -1;
329+
}
330+
311331
/**
312332
* Find multiple strings inside the memory.
313333
* It will start searching with the first string and continue with the next at the position where the previous was found.
Lines changed: 10 additions & 75 deletions
Original file line numberDiff line numberDiff line change
@@ -1,86 +1,21 @@
11
package de.labystudio.spotifyapi.platform.windows.api.playback;
22

3-
import de.labystudio.spotifyapi.platform.windows.api.WinProcess;
3+
public interface PlaybackAccessor {
4+
boolean update();
45

5-
/**
6-
* Accessor to read the duration, position and playing state from the Spotify process.
7-
*
8-
* @author LabyStudio
9-
*/
10-
public class PlaybackAccessor {
6+
boolean isValid();
117

12-
private static final long MIN_TRACK_DURATION = 1000; // 1 second
13-
private static final long MAX_TRACK_DURATION = 1000 * 60 * 10; // 10 minutes
8+
int getLength();
149

15-
private final WinProcess process;
16-
private final PointerRegistry pointerRegistry;
10+
int getPosition();
1711

18-
private int length;
19-
private int position;
20-
private boolean isPlaying;
12+
boolean isPlaying();
2113

22-
/**
23-
* Creates a new instance of the PlaybackAccessor.
24-
*
25-
* @param process The Spotify process to read from.
26-
* @param address The reference address of the playback section
27-
*/
28-
public PlaybackAccessor(WinProcess process, long address) {
29-
this.process = process;
30-
31-
// Create pointer registry to calculate the absolute addresses using the relative offsets
32-
this.pointerRegistry = new PointerRegistry(0x0CFF4498, address);
33-
this.pointerRegistry.register("position", 0x0CFF4810);
34-
this.pointerRegistry.register("length", 0x0CFF4820);
35-
this.pointerRegistry.register("is_playing", 0x0CFF4850); // 1=true, 0=false
36-
37-
this.update();
38-
}
39-
40-
/**
41-
* Read the current length, position and playing state from the Spotify process.
42-
*
43-
* @return true if the new values are valid, false otherwise
44-
*/
45-
public boolean update() {
46-
this.position = this.process.readInteger(this.pointerRegistry.getAddress("position"));
47-
this.length = this.process.readInteger(this.pointerRegistry.getAddress("length"));
48-
this.isPlaying = this.process.readBoolean(this.pointerRegistry.getAddress("is_playing"));
49-
return this.isValid();
50-
}
51-
52-
/**
53-
* Checks if the current values are valid.
54-
* <p>
55-
* To make sure that we have correct values from the memory address,
56-
* we have to set some rules what kind of duration is correct.
57-
* <p>
58-
* The values are correct if:<br>
59-
* - position {@literal <}= length<br>
60-
* - length {@literal >} 0<br>
61-
* - length {@literal <}= 10 minutes<br>
62-
* - position {@literal >}= 1 second<br>
63-
* - the parity bits are correct<br>
64-
*
65-
* @return true if the current values are valid, false otherwise
66-
*/
67-
public boolean isValid() {
68-
return this.position <= this.length
69-
&& this.position >= 0
70-
&& this.length <= MAX_TRACK_DURATION
71-
&& this.length >= MIN_TRACK_DURATION;
14+
default boolean hasTrackLength() {
15+
return this.getLength() > 0;
7216
}
7317

74-
public int getLength() {
75-
return this.length;
18+
default boolean hasTrackPosition() {
19+
return this.getPosition() >= 0 && (!this.hasTrackLength() || this.getPosition() <= this.getLength());
7620
}
77-
78-
public int getPosition() {
79-
return this.position;
80-
}
81-
82-
public boolean isPlaying() {
83-
return this.isPlaying;
84-
}
85-
8621
}
Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
package de.labystudio.spotifyapi.platform.windows.api.playback;
2+
3+
import de.labystudio.spotifyapi.platform.windows.api.spotify.SpotifyProcess;
4+
5+
public class PseudoPlaybackAccessor implements PlaybackAccessor {
6+
7+
private final SpotifyProcess spotifyProcess;
8+
private boolean playing;
9+
10+
public PseudoPlaybackAccessor(SpotifyProcess spotifyProcess) {
11+
this.spotifyProcess = spotifyProcess;
12+
}
13+
14+
@Override
15+
public boolean update() {
16+
this.playing = this.spotifyProcess.isPlayingUsingTitle();
17+
return true;
18+
}
19+
20+
@Override
21+
public boolean isValid() {
22+
return true;
23+
}
24+
25+
@Override
26+
public int getLength() {
27+
return -1;
28+
}
29+
30+
@Override
31+
public int getPosition() {
32+
return -1;
33+
}
34+
35+
@Override
36+
public boolean isPlaying() {
37+
return this.playing;
38+
}
39+
}

0 commit comments

Comments
 (0)