Skip to content

Commit 56ed5f0

Browse files
committed
added pressing media keys
play, pause, stop, next, previous
1 parent 40fe62e commit 56ed5f0

4 files changed

Lines changed: 99 additions & 31 deletions

File tree

native/SMTC4J.cpp

Lines changed: 64 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -10,11 +10,11 @@ using namespace winrt;
1010
using namespace Windows::Media::Control;
1111
using namespace Windows::Storage::Streams;
1212

13-
static std::string base64Encode(const std::vector<uint8_t>& data) {
13+
static std::string base64Encode(const std::vector<uint8_t> &data) {
1414
static auto chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
1515
std::string out;
1616
int val = 0, valb = -6;
17-
for (uint8_t c : data) {
17+
for (uint8_t c: data) {
1818
val = (val << 8) + c;
1919
valb += 8;
2020
while (valb >= 0) {
@@ -28,7 +28,7 @@ static std::string base64Encode(const std::vector<uint8_t>& data) {
2828
return out;
2929
}
3030

31-
static std::string getThumbnailBase64(GlobalSystemMediaTransportControlsSessionMediaProperties const& mediaProps) {
31+
static std::string getThumbnailBase64(GlobalSystemMediaTransportControlsSessionMediaProperties const &mediaProps) {
3232
try {
3333
const auto thumbnail = mediaProps.Thumbnail();
3434
if (!thumbnail) return "";
@@ -51,13 +51,12 @@ static std::string getThumbnailBase64(GlobalSystemMediaTransportControlsSessionM
5151
}
5252

5353
extern "C" {
54-
5554
JNIEXPORT jstring JNICALL
56-
Java_dev_codeman_smtc4j_SMTC4J_getPlaybackState(JNIEnv* env, jclass) {
55+
Java_dev_codeman_smtc4j_SMTC4J_getPlaybackState(JNIEnv *env, jclass) {
5756
try {
5857
try {
5958
winrt::init_apartment();
60-
} catch (const winrt::hresult_invalid_argument&) {
59+
} catch (const winrt::hresult_invalid_argument &) {
6160
}
6261

6362
const auto manager = GlobalSystemMediaTransportControlsSessionManager::RequestAsync().get();
@@ -69,30 +68,34 @@ Java_dev_codeman_smtc4j_SMTC4J_getPlaybackState(JNIEnv* env, jclass) {
6968

7069
int stateCode = 0;
7170
switch (status) {
72-
case GlobalSystemMediaTransportControlsSessionPlaybackStatus::Playing: stateCode = 2; break;
73-
case GlobalSystemMediaTransportControlsSessionPlaybackStatus::Paused: stateCode = 1; break;
74-
case GlobalSystemMediaTransportControlsSessionPlaybackStatus::Stopped: stateCode = 0; break;
75-
default: stateCode = -1; break;
71+
case GlobalSystemMediaTransportControlsSessionPlaybackStatus::Playing: stateCode = 2;
72+
break;
73+
case GlobalSystemMediaTransportControlsSessionPlaybackStatus::Paused: stateCode = 1;
74+
break;
75+
case GlobalSystemMediaTransportControlsSessionPlaybackStatus::Stopped: stateCode = 0;
76+
break;
77+
default: stateCode = -1;
78+
break;
7679
}
7780

7881
const auto timeline = session.GetTimelineProperties();
7982
double positionSec = timeline.Position().count() / 10'000'000.0; // to seconds
8083

81-
if (stateCode == 2) { // update position based on elapsed time while playing
84+
if (stateCode == 2) {
85+
// update position based on elapsed time while playing
8286
const auto lastUpdated = timeline.LastUpdatedTime().time_since_epoch();
8387
const auto now = winrt::clock::now().time_since_epoch();
8488
const auto deltaTicks = now.count() - lastUpdated.count();
8589
positionSec += deltaTicks / 10'000'000.0; // to seconds
8690
}
8791

8892
const std::string json = "{"
89-
"\"stateCode\":" + std::to_string(stateCode) + ","
90-
"\"position\":" + std::to_string(positionSec) +
91-
"}";
93+
"\"stateCode\":" + std::to_string(stateCode) + ","
94+
"\"position\":" + std::to_string(positionSec) +
95+
"}";
9296

9397
return env->NewStringUTF(json.c_str());
94-
95-
} catch (const winrt::hresult_error& e) {
98+
} catch (const winrt::hresult_error &e) {
9699
std::string err = std::string(R"({"error":")") + winrt::to_string(e.message()) + "\"}";
97100
return env->NewStringUTF(err.c_str());
98101
} catch (...) {
@@ -101,11 +104,11 @@ Java_dev_codeman_smtc4j_SMTC4J_getPlaybackState(JNIEnv* env, jclass) {
101104
}
102105

103106
JNIEXPORT jstring JNICALL
104-
Java_dev_codeman_smtc4j_SMTC4J_getMediaInfo(JNIEnv* env, jclass) {
107+
Java_dev_codeman_smtc4j_SMTC4J_getMediaInfo(JNIEnv *env, jclass) {
105108
try {
106109
try {
107110
winrt::init_apartment();
108-
} catch (const winrt::hresult_invalid_argument&) {
111+
} catch (const winrt::hresult_invalid_argument &) {
109112
}
110113

111114
const auto manager = GlobalSystemMediaTransportControlsSessionManager::RequestAsync().get();
@@ -120,22 +123,56 @@ Java_dev_codeman_smtc4j_SMTC4J_getMediaInfo(JNIEnv* env, jclass) {
120123
const auto sourceApp = winrt::to_string(session.SourceAppUserModelId());
121124

122125
const std::string json = "{"
123-
"\"title\":\"" + winrt::to_string(mediaProps.Title()) + "\","
124-
"\"artist\":\"" + winrt::to_string(mediaProps.Artist()) + "\","
125-
"\"album\":\"" + winrt::to_string(mediaProps.AlbumTitle()) + "\","
126-
"\"duration\":" + std::to_string(durationSec) + ","
127-
"\"sourceApp\":\"" + sourceApp + "\","
128-
"\"thumbnailBase64\":\"" + thumbnailBase64 + "\""
129-
"}";
126+
"\"title\":\"" + winrt::to_string(mediaProps.Title()) + "\","
127+
"\"artist\":\"" + winrt::to_string(mediaProps.Artist()) + "\","
128+
"\"album\":\"" + winrt::to_string(mediaProps.AlbumTitle()) + "\","
129+
"\"duration\":" + std::to_string(durationSec) + ","
130+
"\"sourceApp\":\"" + sourceApp + "\","
131+
"\"thumbnailBase64\":\"" + thumbnailBase64 + "\""
132+
"}";
130133

131134
return env->NewStringUTF(json.c_str());
132-
133-
} catch (const winrt::hresult_error& e) {
135+
} catch (const winrt::hresult_error &e) {
134136
const std::string err = std::string(R"({"error":")") + winrt::to_string(e.message()) + "\"}";
135137
return env->NewStringUTF(err.c_str());
136138
} catch (...) {
137139
return env->NewStringUTF(R"({"error":"unknown exception"})");
138140
}
139141
}
140142

143+
JNIEXPORT void JNICALL
144+
Java_dev_codeman_smtc4j_SMTC4J_pressMediaKey(JNIEnv *, jclass, jint keyCode) {
145+
try {
146+
try {
147+
winrt::init_apartment();
148+
} catch (const winrt::hresult_invalid_argument &) {
149+
}
150+
151+
const auto manager = GlobalSystemMediaTransportControlsSessionManager::RequestAsync().get();
152+
const auto session = manager.GetCurrentSession();
153+
if (!session) return;
154+
155+
switch (keyCode) {
156+
case 0: // Play
157+
session.TryPlayAsync().get();
158+
break;
159+
case 1: // Pause
160+
session.TryPauseAsync().get();
161+
break;
162+
case 2: // Stop
163+
session.TryStopAsync().get();
164+
break;
165+
case 3: // Next
166+
session.TrySkipNextAsync().get();
167+
break;
168+
case 4: // Previous
169+
session.TrySkipPreviousAsync().get();
170+
break;
171+
default:
172+
break;
173+
}
174+
} catch (...) {
175+
// Ignore exceptions for media key presses
176+
}
177+
}
141178
}
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
package dev.codeman.smtc4j;
2+
3+
public enum MediaKey {
4+
PLAY, PAUSE, STOP, NEXT, PREVIOUS
5+
}

src/main/java/dev/codeman/smtc4j/SMTC4J.java

Lines changed: 14 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ public class SMTC4J {
1717

1818
private static native String getPlaybackState();
1919
private static native String getMediaInfo();
20+
private static native void pressMediaKey(int keyCode);
2021

2122
public static boolean load() {
2223
if (loaded) return true;
@@ -82,8 +83,7 @@ public static void startUpdateThread(long intervalMillis) {
8283
}
8384

8485
public static MediaInfo parsedMediaInfo() {
85-
if (!isLoaded())
86-
throw new IllegalStateException("SMTC4J native library is not loaded.");
86+
checkIsLoaded();
8787

8888
String info = getMediaInfo();
8989

@@ -100,8 +100,7 @@ public static MediaInfo parsedMediaInfo() {
100100
}
101101

102102
public static PlaybackState parsedPlaybackState() {
103-
if (!isLoaded())
104-
throw new IllegalStateException("SMTC4J native library is not loaded.");
103+
checkIsLoaded();
105104

106105
String state = getPlaybackState();
107106

@@ -116,4 +115,15 @@ public static PlaybackState parsedPlaybackState() {
116115

117116
return parsed;
118117
}
118+
119+
public static void pressKey(MediaKey key) {
120+
checkIsLoaded();
121+
122+
pressMediaKey(key.ordinal());
123+
}
124+
125+
private static void checkIsLoaded() {
126+
if (!isLoaded())
127+
throw new IllegalStateException("SMTC4J native library is not loaded.");
128+
}
119129
}

src/test/java/Main.java

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,9 @@
1+
import dev.codeman.smtc4j.MediaKey;
2+
import dev.codeman.smtc4j.PlaybackState;
13
import dev.codeman.smtc4j.SMTC4J;
24

5+
import java.util.concurrent.ThreadLocalRandom;
6+
37
public class Main {
48

59
public static void main(String[] args) {
@@ -14,15 +18,27 @@ public static void main(String[] args) {
1418

1519
SMTC4J.startUpdateThread(1000);
1620

21+
int i = 0;
22+
1723
while (true) {
1824
try {
1925
Thread.sleep(1000);
2026
} catch (InterruptedException e) {
2127
e.printStackTrace();
2228
}
29+
2330
System.out.println("State: " + SMTC4J.getCachedPlaybackState());
2431
System.out.println("State Code: " + SMTC4J.getCachedPlaybackState().getStateCode().name());
2532
System.out.println("Info: " + SMTC4J.getCachedMediaInfo());
33+
34+
35+
if (i % 5 == 0) {
36+
MediaKey keyToPress = MediaKey.values()[ThreadLocalRandom.current().nextInt(0, MediaKey.values().length)];
37+
SMTC4J.pressKey(keyToPress);
38+
System.out.println("Pressed key: " + keyToPress.name());
39+
}
40+
41+
i++;
2642
}
2743
}
2844

0 commit comments

Comments
 (0)