Skip to content

Instantly share code, notes, and snippets.

@ericlewis
Last active August 29, 2024 15:00
Show Gist options
  • Save ericlewis/bb6ba191d090148794b70344e050b717 to your computer and use it in GitHub Desktop.
Save ericlewis/bb6ba191d090148794b70344e050b717 to your computer and use it in GitHub Desktop.
package humaneinternal.system.voice;
import android.content.Context;
import androidx.annotation.NonNull;
import com.shazam.shazamkit.AudioSampleRateInHz;
import com.shazam.shazamkit.Catalog;
import com.shazam.shazamkit.DeveloperToken;
import com.shazam.shazamkit.DeveloperTokenProvider;
import com.shazam.shazamkit.MatchResult;
import com.shazam.shazamkit.Session;
import com.shazam.shazamkit.ShazamCatalog;
import com.shazam.shazamkit.ShazamKit;
import com.shazam.shazamkit.ShazamKitException;
import com.shazam.shazamkit.ShazamKitResult;
import com.shazam.shazamkit.Signature;
import com.shazam.shazamkit.SignatureGenerator;
import com.shazam.shazamkit.StreamingSession;
import java.util.Locale;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import kotlin.coroutines.Continuation;
import kotlin.coroutines.CoroutineContext;
import kotlinx.coroutines.Dispatchers;
import kotlinx.coroutines.flow.Flow;
import kotlinx.coroutines.flow.FlowCollector;
public class ShazamManager {
private final ExecutorService executorService = Executors.newSingleThreadExecutor();
private final Context context;
private final String developerToken;
private ShazamCatalog shazamCatalog;
private SignatureGenerator signatureGenerator;
private StreamingSession streamingSession;
public ShazamManager(Context context, String developerToken) {
this.context = context;
this.developerToken = developerToken;
initialize();
}
private void initialize() {
DeveloperTokenProvider tokenProvider = () -> new DeveloperToken(developerToken);
shazamCatalog = ShazamKit.INSTANCE.createShazamCatalog(tokenProvider, Locale.getDefault());
}
public void initializeSignatureGenerator(AudioSampleRateInHz sampleRate, SignatureGeneratorCallback callback) {
CompletableFuture.supplyAsync(() -> {
try {
ShazamKitResult<ShazamKitException, SignatureGenerator> result = suspendToBlocking(
continuation -> ShazamKit.INSTANCE.createSignatureGenerator(sampleRate, continuation)
);
if (result instanceof ShazamKitResult.Success) {
signatureGenerator = ((ShazamKitResult.Success<SignatureGenerator>) result).getData();
return true;
} else {
throw ((ShazamKitResult.Failure<ShazamKitException>) result).getReason();
}
} catch (Exception e) {
throw new RuntimeException(e);
}
}, executorService).thenAccept(success -> callback.onSuccess())
.exceptionally(e -> {
callback.onError((Exception) e);
return null;
});
}
public void appendAudioData(byte[] audioData, int meaningfulLengthInBytes, long timestamp) {
if (signatureGenerator == null) {
throw new IllegalStateException("SignatureGenerator not initialized");
}
signatureGenerator.append(audioData, meaningfulLengthInBytes, timestamp);
}
public void generateSignature(SignatureCallback callback) {
if (signatureGenerator == null) {
callback.onError(new IllegalStateException("SignatureGenerator not initialized"));
return;
}
Signature signature = signatureGenerator.generateSignature();
callback.onSignatureGenerated(signature);
}
public void matchSignature(Signature signature, MatchResultCallback callback) {
CompletableFuture.supplyAsync(() -> {
try {
ShazamKitResult<ShazamKitException, Session> sessionResult = suspendToBlocking(
continuation -> ShazamKit.INSTANCE.createSession(shazamCatalog, continuation)
);
if (sessionResult instanceof ShazamKitResult.Success) {
Session session = ((ShazamKitResult.Success<Session>) sessionResult).getData();
return suspendToBlocking(continuation -> session.match(signature, continuation));
} else {
throw ((ShazamKitResult.Failure<ShazamKitException>) sessionResult).getReason();
}
} catch (Exception e) {
throw new RuntimeException(e);
}
}, executorService).thenAccept(matchResult -> callback.onMatchResult((MatchResult) matchResult))
.exceptionally(e -> {
callback.onError((Exception) e);
return null;
});
}
public void initializeStreamingSession(AudioSampleRateInHz sampleRate, int bufferSize, StreamingSessionCallback callback) {
CompletableFuture.supplyAsync(() -> {
try {
ShazamKitResult<ShazamKitException, StreamingSession> result = suspendToBlocking(
continuation -> ShazamKit.INSTANCE.createStreamingSession(shazamCatalog, sampleRate, bufferSize, continuation)
);
if (result instanceof ShazamKitResult.Success) {
streamingSession = ((ShazamKitResult.Success<StreamingSession>) result).getData();
return true;
} else {
throw ((ShazamKitResult.Failure<ShazamKitException>) result).getReason();
}
} catch (Exception e) {
throw new RuntimeException(e);
}
}, executorService).thenAccept(success -> callback.onSuccess())
.exceptionally(e -> {
callback.onError((Exception) e);
return null;
});
}
public void matchStream(byte[] audioData, int meaningfulLengthInBytes, long timestampInMs) {
if (streamingSession == null) {
throw new IllegalStateException("StreamingSession not initialized");
}
streamingSession.matchStream(audioData, meaningfulLengthInBytes, timestampInMs);
}
public void startStreamingRecognition(final StreamingRecognitionCallback callback) {
if (streamingSession == null) {
callback.onError(new IllegalStateException("StreamingSession not initialized"));
return;
}
CompletableFuture.runAsync(() -> {
Flow<MatchResult> flow = streamingSession.recognitionResults();
try {
suspendToBlocking(continuation -> flow.collect(new FlowCollector<MatchResult>() {
@Override
public Object emit(MatchResult matchResult, @NonNull Continuation<? super kotlin.Unit> emitContinuation) {
callback.onRecognitionResult(matchResult);
return kotlin.Unit.INSTANCE;
}
}, continuation));
} catch (Exception e) {
callback.onError(e);
}
}, executorService);
}
public void shutdown() {
executorService.shutdown();
}
private <T> T suspendToBlocking(SuspendFunction<T> function) throws Exception {
final Object[] result = new Object[1];
final Exception[] exception = new Exception[1];
function.invoke(new Continuation<T>() {
@NonNull
@Override
public CoroutineContext getContext() {
return Dispatchers.getDefault();
}
@Override
public void resumeWith(@NonNull Object o) {
if (o instanceof kotlin.Result.Failure) {
exception[0] = ((kotlin.Result.Failure) o).exception;
} else {
result[0] = o;
}
}
});
if (exception[0] != null) {
throw exception[0];
}
return (T) result[0];
}
private interface SuspendFunction<T> {
void invoke(Continuation<? super T> continuation);
}
public interface SignatureGeneratorCallback {
void onSuccess();
void onError(Exception e);
}
public interface SignatureCallback {
void onSignatureGenerated(Signature signature);
void onError(Exception e);
}
public interface MatchResultCallback {
void onMatchResult(MatchResult matchResult);
void onError(Exception e);
}
public interface StreamingSessionCallback {
void onSuccess();
void onError(Exception e);
}
public interface StreamingRecognitionCallback {
void onRecognitionResult(MatchResult matchResult);
void onError(Exception e);
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment