上一篇文章介绍了验证api_key.txt的流程,这首先是开发AlexaAndroid的第一步。在基于AlexaAndroid项目的基础上要分析项目的源码之后,根据的需求去进行下一步的开发。这样就要看懂AlexaAndroid关于语音识别的整个流程。
其实语音识别是亚马逊后台Alexa Service后台在做的,我们只要接入:
1、登录亚马逊的模块;
2、语音收集的模块;
3、把语音以数据流的方式通过网络传入Alexa;
4、Alexa Service后台返回给我的数据后解析成语音播放。
一、亚马逊的登录验证:
首先,在通过网络发送语音数据流到Alexa后台时,需要对账号进行登录验证。以下是以AlexaAndroid开源项目的源码分析。
登录验证则要跳转到浏览器进行登录验证。在AndroidManifest.xml对AuthorizationActivity注册。
<activity
android:name="com.amazon.identity.auth.device.authorization.AuthorizationActivity"
android:allowTaskReparenting="true"
android:launchMode="singleTask"
android:theme="@android:style/Theme.NoDisplay" >
<intent-filter>
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.BROWSABLE" />
<!-- host should be our application package //-->
<data
android:host="com.willblaschko.android.avs"
android:scheme="amzn" />
</intent-filter>
</activity>
1.在SendAudioActionFragment中的alexaManager.sendAudioRequest()方法中验证了账号的登录,在发送授权token之前是要对账号登录验证:
alexaManager.sendAudioRequest(requestBody, getRequestCallback());
public void sendAudioRequest(final DataRequestBody requestBody, @Nullable final AsyncCallback<AvsResponse, Exception> callback){
//check if the user is already logged in
mAuthorizationManager.checkLoggedIn(mContext, new ImplCheckLoggedInCallback() {
@Override
public void success(Boolean result) {
if (result) {
//if the user is logged in
//set our URL
final String url = getEventsUrl();
//get our access token
TokenManager.getAccessToken(mAuthorizationManager.getAmazonAuthorizationManager(), mContext, new TokenManager.TokenCallback() {
@Override
public void onSuccess(final String token) {
//do this off the main thread
new AsyncTask<Void, Void, AvsResponse>() {
@Override
protected AvsResponse doInBackground(Void... params) {
try {
getSpeechSendAudio().sendAudio(url, token, requestBody, new AsyncEventHandler(AlexaManager.this, callback));
} catch (IOException e) {
e.printStackTrace();
//bubble up the error
if(callback != null) {
callback.failure(e);
}
}
return null;
}
@Override
protected void onPostExecute(AvsResponse avsResponse) {
super.onPostExecute(avsResponse);
}
}.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
}
@Override
public void onFailure(Throwable e) {
}
});
} else {
//if the user is not logged in, log them in and then call the function again
logIn(new ImplAuthorizationCallback<AvsResponse>(callback) {
@Override
public void onSuccess() {
//call our function again
sendAudioRequest(requestBody, callback);
}
});
}
}
});
}
public void logIn(@Nullable final AuthorizationCallback callback){
//check if we're already logged in
mAuthorizationManager.checkLoggedIn(mContext, new AsyncCallback<Boolean, Throwable>() {
@Override
public void start() {
}
@Override
public void success(Boolean result) {
//if we are, return a success
if(result){
if(callback != null){
callback.onSuccess();
}
}else{
//otherwise start the authorization process
//拼接授权登录的URL到AuthorizationActivity下登录验证
mAuthorizationManager.authorizeUser(callback);
}
}
@Override
public void failure(Throwable error) {
if(callback != null) {
callback.onError(new Exception(error));
}
}
@Override
public void complete() {
}
});
}
2.AuthorizationManager的AuthorizeUser()方法中会拼接URL所需要的数据:
public void authorizeUser(AuthorizationCallback callback){
mCallback = callback;
String PRODUCT_DSN = Settings.Secure.getString(mContext.getContentResolver(), Settings.Secure.ANDROID_ID);
Bundle options = new Bundle();
String scope_data = "{\"alexa:all\":{\"productID\":\"" + mProductId +
"\", \"productInstanceAttributes\":{\"deviceSerialNumber\":\"" +PRODUCT_DSN + "\"}}}";
options.putString(AuthzConstants.BUNDLE_KEY.SCOPE_DATA.val, scope_data);
options.putBoolean(AuthzConstants.BUNDLE_KEY.GET_AUTH_CODE.val, true);
options.putString(AuthzConstants.BUNDLE_KEY.CODE_CHALLENGE.val, getCodeChallenge());
options.putString(AuthzConstants.BUNDLE_KEY.CODE_CHALLENGE_METHOD.val, "S256");
//拼接URL数据规则后传递给在login-with-amazon-sdk.jar中的AmazonAuthorizationManager处理
mAuthManager.authorize(APP_SCOPES, options, authListener);
}
3.数据由亚马逊账号登录的jar包ogin-with-amazon-sdk.jar中类AmazonAuthorizationManager -->InternalAuthManager中的authorize()方法中:
/** @deprecated */
@Deprecated
public Future<Bundle> authorize(String[] scopes, Bundle options, AuthorizationListener listener) {
return InternalAuthManager.getInstance(this.mContext).authorize((AuthorizeRequest)null, this.mContext, scopes, options, listener);
}
public Future<Bundle> authorize(final AuthorizeRequest request, final Context context, final String[] scopes, final Bundle options, final AuthorizationListener listener) {
if(scopes != null && scopes.length != 0) {
MAPLog.i(LOG_TAG, context.getPackageName() + " calling authorize: scopes=" + Arrays.toString(scopes));
ThreadUtils.THREAD_POOL.execute(new Runnable() {
public void run() {
if(!InternalAuthManager.this.isAPIKeyValid(context)) {
listener.onError(new AuthError("APIKey is invalid", ERROR_TYPE.ERROR_ACCESS_DENIED));
} else {
Bundle allOptions = options == null?new Bundle():new Bundle(options);
if(!allOptions.containsKey(BUNDLE_KEY.SANDBOX.val)) {
allOptions.putBoolean(BUNDLE_KEY.SANDBOX.val, AuthorizationManager.isSandboxMode(context));
}
//创建第三方授权登录的帮助类
ThirdPartyAuthorizationHelper authzHelper = new ThirdPartyAuthorizationHelper();
try {//从这里可启动浏览器进行账号的登录验证:
authzHelper.authorize(request, context, context.getPackageName(), InternalAuthManager.this.clientId, InternalAuthManager.this.getRedirectURI(context), scopes, true, InternalAuthManager.tokenVendor, listener, allOptions);
} catch (AuthError var4) {
listener.onError(var4);
}
}
}
});
return null;
} else {
throw new IllegalArgumentException("scopes must not be null or empty!");
}
}
4.ThirdPartyAuthorizationHelper的authorize()方法中:
public void authorize(final AuthorizeRequest originalRequest, final Context context, String packageName, final String clientId, String redirectURI, String[] requestedScopes, final boolean isBrowserFlow, TokenVendor tokenVendor, final AuthorizationListener listener, Bundle options) throws AuthError {
if(ThreadUtils.isRunningOnMainThread()) {
MAPLog.e(LOG_TAG, "authorize started on main thread");
throw new IllegalStateException("authorize started on main thread");
} else {
AppIdentifier appIdentifier = new ThirdPartyAppIdentifier();
final AppInfo appInfo = appIdentifier.getAppInfo(packageName, context);
List<RequestedScope> cachedScopes = tokenVendor.getCachedScopes(context);
final String[] allScopes = getCommonScopesForAuthorization(context, requestedScopes, cachedScopes);
final boolean isSandboxMode = options.getBoolean(BUNDLE_KEY.SANDBOX.val, false);
final Bundle extraParameters;
if(options == Bundle.EMPTY) {
extraParameters = new Bundle();
} else {
extraParameters = options;
}
extraParameters.putBoolean(BUNDLE_KEY.CHECK_API_KEY.val, false);
extraParameters.putBoolean(BUNDLE_KEY.RETURN_CODE.val, true);
extraParameters.putString(AUTHORIZE_BUNDLE_KEY.REGION.val, AuthorizationManager.getRegion(context).getStringValue());
extraParameters.putString(BUNDLE_KEY.CLIENT_ID.val, clientId);
extraParameters.putString(BUNDLE_KEY.SDK_VERSION.val, "LWAAndroidSDK3.0.0");
try {
extraParameters.putBundle(BUNDLE_KEY.EXTRA_URL_PARAMS.val, this.getExtraUrlParams(extraParameters));
} catch (AuthError var19) {
listener.onError(var19);
return;
}
Bundle results = Bundle.EMPTY;
if(!isSandboxMode && (StoredPreferences.isTokenObtainedFromSSO(context) || cachedScopes == null || cachedScopes.size() == 0)) {
results = this.startAuthorizationWithService(context, allScopes, extraParameters);
}
if(results.containsKey("code") && !TextUtils.isEmpty(results.getString("code"))) {
if(extraParameters.getBoolean(BUNDLE_KEY.GET_AUTH_CODE.val, false)) {
AuthorizationHelper.sendAuthorizationCodeAsResponse(results.getString("code"), clientId, redirectURI, listener);
return;
}
String codeVerifier = this.codeChallengeWorkflow.getCodeVerifier();
this.handleCodeForTokenExchange(context, packageName, codeVerifier, results, extraParameters, listener);
StoredPreferences.setTokenObtainedFromSSO(context, true);
} else if(!results.containsKey("AUTH_ERROR_EXECEPTION") && !results.containsKey(BUNDLE_KEY.AUTHORIZE.val) && !results.containsKey(BUNDLE_KEY.CAUSE_ID.val)) {
ProfileDataSource.getInstance(context).deleteAllRows();
Handler myHandler = new Handler(Looper.getMainLooper());
myHandler.post(new Runnable() {
public void run() {
try {
if(!isBrowserFlow && !isSandboxMode) {
listener.onError(new AuthError("WebView is not allowed for Authorization", ERROR_TYPE.ERROR_BAD_PARAM));
} else {//跟Browser交互登录验证的方法:
ThirdPartyAuthorizationHelper.this.authorizeWithBrowser(originalRequest, context, context.getPackageName(), clientId, allScopes, listener, extraParameters, appInfo);
StoredPreferences.setTokenObtainedFromSSO(context, false);
}
} catch (AuthError var2) {
listener.onError(var2);
}
}
});
} else {
results.setClassLoader(context.getClassLoader());
if(results.containsKey(BUNDLE_KEY.CAUSE_ID.val)) {
listener.onCancel(results);
} else if(results.containsKey("AUTH_ERROR_EXECEPTION")) {
listener.onError(AuthError.extractError(results));
} else {
DatabaseHelper.clearAuthorizationState(context);
Bundle bundle = new Bundle();
bundle.putString(BUNDLE_KEY.AUTHORIZE.val, "authorized via service");
listener.onSuccess(bundle);
}
}
}
}
private void authorizeWithBrowser(AuthorizeRequest originalRequest, Context context, String packageName, String clientId, String[] scopes, AuthorizationListener listener, Bundle options, AppInfo appInfo) throws AuthError {
options.getBundle(BUNDLE_KEY.EXTRA_URL_PARAMS.val).remove("client_id");
AuthorizationRequest request = new AuthorizationRequest(originalRequest, clientId, scopes, options, appInfo, listener);
RequestManager.getInstance().executeRequest(request, context); //执行浏览器的请求
}
5.RequestManager中的executeRequest():
public void executeRequest(AbstractRequest request, Context context) throws AuthError {
MAPLog.d(LOG_TAG, "Executing request " + request.getRequestId());
if(!request.canAttempt()) {
throw new AuthError(String.format("Reached maximum attempts for the request: %s", new Object[]{request.getRequestId()}), ERROR_TYPE.ERROR_SERVER_REPSONSE);
} else {
request.incrementAttemptCount();
this.cleanupOldActiveRequests();
this.activeRequests.put(request.getRequestId(), request);
this.externalBrowserManager.openUrl(request, request.getUrl(context), context);//这里打开浏览器传入Url
}
}
6.最后的操作是在ExternalBrowserManager中的openUrl()用intent启动浏览器进行登录验证:
public void openUrl(AbstractRequest request, String url, Context context) throws AuthError {
CompatibilityUtil.assertCorrectManifestIntegration(context); //manifest注册的activity
Intent intent = this.getIntent(url, context);
MAPLog.i(LOG_TAG, "Starting External Browser");
try {
request.onStart();
context.startActivity(intent); //打开activity
} catch (Exception var6) {
MAPLog.e(LOG_TAG, "Unable to Launch Browser: " + var6.getMessage());
throw new AuthError("Unable to Launch Browser.", var6, ERROR_TYPE.ERROR_UNKNOWN);
}
}
7.如在浏览器登录成功之后返回在AuthorizationManager的AuthorizeUser()的回调中TokenManager保存token的值及刷新token值:
private AuthorizationListener authListener = new AuthorizationListener() {
/**
* Authorization was completed successfully.
* Display the profile of the user who just completed authorization
* @param response bundle containing authorization response. Not used.
*/
@Override
public void onSuccess(Bundle response) {
String authCode = response.getString(AuthzConstants.BUNDLE_KEY.AUTHORIZATION_CODE.val);
if(BuildConfig.DEBUG) {
Log.i(TAG, "Authorization successful");
Util.showAuthToast(mContext, "Authorization successful.");
}
//登录回调成功的处理进行token解析及保存
TokenManager.getAccessToken(mContext, authCode, getCodeVerifier(), mAuthManager, new TokenManager.TokenResponseCallback() {
@Override
public void onSuccess(TokenManager.TokenResponse response) {
if(mCallback != null){
mCallback.onSuccess();
}
}
@Override
public void onFailure(Exception error) {
if(mCallback != null){
mCallback.onError(error);
}
}
});
}
public static void getAccessToken(final Context context, @NotNull String authCode, @NotNull String codeVerifier, AmazonAuthorizationManager authorizationManager, @Nullable final TokenResponseCallback callback){
//this url shouldn't be hardcoded, but it is, it's the Amazon auth access token endpoint
String url = "https://api.amazon.com/auth/O2/token";
//set up our arguments for the api call, these will be the call headers
FormBody.Builder builder = new FormBody.Builder()
.add(ARG_GRANT_TYPE, "authorization_code")
.add(ARG_CODE, authCode);
try {
builder.add(ARG_REDIRECT_URI, authorizationManager.getRedirectUri());
builder.add(ARG_CLIENT_ID, authorizationManager.getClientId());
} catch (AuthError authError) {
authError.printStackTrace();
}
builder.add(ARG_CODE_VERIFIER, codeVerifier);
OkHttpClient client = ClientUtil.getTLS12OkHttpClient();
Request request = new Request.Builder()
.url(url)
.post(builder.build())
.build();
final Handler handler = new Handler(Looper.getMainLooper());
client.newCall(request).enqueue(new Callback() {
@Override
public void onFailure(Call call, final IOException e) {
e.printStackTrace();
if(callback != null){
//bubble up error
handler.post(new Runnable() {
@Override
public void run() {
callback.onFailure(e);
}
});
}
}
@Override
public void onResponse(Call call, Response response) throws IOException {
String s = response.body().string();
if(BuildConfig.DEBUG) {
Log.i(TAG, s);
}
final TokenResponse tokenResponse = new Gson().fromJson(s, TokenResponse.class);
//save our tokens to local shared preferences 保存回调回来的token的值
saveTokens(context, tokenResponse);
if(callback != null){
//bubble up success
handler.post(new Runnable() {
@Override
public void run() {
callback.onSuccess(tokenResponse);
}
});
}
}
});
}
二、用户语音的收集过程:
1.首先是初始话RawAudioRecorder对象,用回调DataRequestBody收集到用户的语音输入:
private DataRequestBody requestBody = new DataRequestBody() {
@Override
public void writeTo(BufferedSink sink) throws IOException {
while (recorder != null && !recorder.isPausing()) { //以此判断用户输入是否完成
if(recorder != null) {
final float rmsdb = recorder.getRmsdb();
if(recorderView != null) {
recorderView.post(new Runnable() {
@Override
public void run() {
recorderView.setRmsdbLevel(rmsdb);
}
});
}
if(sink != null && recorder != null) {
sink.write(recorder.consumeRecording());
}
if(BuildConfig.DEBUG){
Log.i(TAG, "Received audio");
Log.e(TAG, "RMSDB: " + rmsdb);
}
}
try {
Thread.sleep(25);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
stopListening();
}
};
三.收集到语音之后以数据流拼接成http的请求通过网络发送到Alexa:
1.AlexaManager获取token成功之后:
//get our access token
TokenManager.getAccessToken(mAuthorizationManager.getAmazonAuthorizationManager(), mContext, new TokenManager.TokenCallback() {
@Override
public void onSuccess(final String token) {
//do this off the main thread
new AsyncTask<Void, Void, AvsResponse>() {
@Override
protected AvsResponse doInBackground(Void... params) {
try { //拼接requestBody发送语音流数据
getSpeechSendAudio().sendAudio(url, token, requestBody, new AsyncEventHandler(AlexaManager.this, callback));
} catch (IOException e) {
e.printStackTrace();
//bubble up the error
if(callback != null) {
callback.failure(e);
}
}
return null;
}
@Override
protected void onPostExecute(AvsResponse avsResponse) {
super.onPostExecute(avsResponse);
}
}.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
}
@Override
public void onFailure(Throwable e) {
}
});
2.SpeechSendAudio类中sendAudio()方法:
public void sendAudio(final String url, final String accessToken, @NotNull DataRequestBody requestBody,
final AsyncCallback<Call, Exception> callback) throws IOException {
this.requestBody = requestBody; //请求体会在SpeechSendEvent以audio文件格式添加
if(callback != null){
callback.start();
}
Log.i(TAG, "Starting SpeechSendAudio procedure");
start = System.currentTimeMillis();
//call the parent class's prepareConnection() in order to prepare our URL POST
try {
prepareConnection(url, accessToken); //拼接好请求头所需头数据
final Call response = completePost(); //返回响应的数据
if (callback != null) {
if (response != null) {
callback.success(response);
}
callback.complete();
}
Log.i(TAG, "Audio sent");
Log.i(TAG, "Audio sending process took: " + (System.currentTimeMillis() - start));
} catch (IOException|AvsException e) {
onError(callback, e);
}
}
3.SpeechSendAudio父类sendEvent中请求和响应做出了处理:
protected void prepareConnection(String url, String accessToken) {
//set the request URL
mRequestBuilder.url(url);
//set our authentication access token header
mRequestBuilder.addHeader("Authorization", "Bearer " + accessToken);
String event = getEvent();
mBodyBuilder = new MultipartBody.Builder()
.setType(MultipartBody.FORM)
.addFormDataPart("metadata", "metadata", RequestBody.create(MediaType.parse("application/json; charset=UTF-8"), event));
//reset our output stream
mOutputStream = new ByteArrayOutputStream();
}
protected Call completePost() throws IOException, AvsException, RuntimeException {
addFormDataParts(mBodyBuilder);
mRequestBuilder.post(mBodyBuilder.build());
return parseResponse();
}
四、网络响应返回的数据的解析:
1. getRequstCallback(),BaseActivity用异步请求回调的方式:
alexaManager.sendAudioRequest(requestBody, getRequestCallback());
//async callback for commands sent to Alexa Voice
private AsyncCallback<AvsResponse, Exception> requestCallback = new AsyncCallback<AvsResponse, Exception>() {
@Override
public void start() {
startTime = System.currentTimeMillis();
Log.i(TAG, "Event Start");
setState(STATE_PROCESSING);
}
@Override
public void success(AvsResponse result) {
Log.i(TAG, "Event Success");
//解析从Alexa返回的数据
Log.e(TAG, "success:处理从Alexa返回回来的数据:"+result.toString());
handleResponse(result);
}
@Override
public void failure(Exception error) {
error.printStackTrace();
Log.i(TAG, "Event Error");
setState(STATE_FINISHED);
}
@Override
public void complete() {
Log.i(TAG, "Event Complete");
new Handler(Looper.getMainLooper()).post(new Runnable() {
@Override
public void run() {
long totalTime = System.currentTimeMillis() - startTime;
Toast.makeText(BaseActivity.this, "Total request time: " + totalTime + " miliseconds", Toast.LENGTH_LONG).show();
//Log.i(TAG, "Total request time: "+totalTime+" miliseconds");
}
});
}
};
2.handleResonse()对返回数据检查,保存在List<AvsItem>数组中:
private void handleResponse(AvsResponse response) {
boolean checkAfter = (avsQueue.size() == 0);
if (response != null) {
//if we have a clear queue item in the list, we need to clear the current queue before proceeding
//iterate backwards to avoid changing our array positions and getting all the nasty errors that come
//from doing that
for (int i = response.size() - 1; i >= 0; i--) {
if (response.get(i) instanceof AvsReplaceAllItem || response.get(i) instanceof AvsReplaceEnqueuedItem) {
//clear our queue
avsQueue.clear();
//remove item
response.remove(i);
}
}
Log.i(TAG, "Adding " + response.size() + " items to our queue");
if (BuildConfig.DEBUG) {
for (int i = 0; i < response.size(); i++) {
Log.i(TAG, "\tAdding: " + response.get(i).getToken());
}
}
avsQueue.addAll(response);
}
if (checkAfter) {
checkQueue();
}
}
3.而在chekQueue中对AvsItem子类类型分别做了处理:
private void checkQueue() {
//if we're out of things, hang up the phone and move on
if (avsQueue.size() == 0) {
setState(STATE_FINISHED);
new Handler(Looper.getMainLooper()).post(new Runnable() {
@Override
public void run() {
long totalTime = System.currentTimeMillis() - startTime;
Toast.makeText(BaseActivity.this, "Total interaction time: " + totalTime + " miliseconds", Toast.LENGTH_LONG).show();
Log.i(TAG, "Total interaction time: " + totalTime + " miliseconds");
}
});
return;
}
final AvsItem current = avsQueue.get(0);
Log.i(TAG, "Item type " + current.getClass().getName());
if (current instanceof AvsPlayRemoteItem) {
//play a URL
if (!audioPlayer.isPlaying()) {
audioPlayer.playItem((AvsPlayRemoteItem) current);
}
} else if (current instanceof AvsPlayContentItem) {
//play a URL
if (!audioPlayer.isPlaying()) {
audioPlayer.playItem((AvsPlayContentItem) current);
}
} else if (current instanceof AvsSpeakItem) {
//play a sound file
if (!audioPlayer.isPlaying()) {
audioPlayer.playItem((AvsSpeakItem) current);
}
setState(STATE_SPEAKING);
} else if (current instanceof AvsStopItem) {
//stop our play
audioPlayer.stop();
avsQueue.remove(current);
} else if (current instanceof AvsReplaceAllItem) {
//clear all items
//mAvsItemQueue.clear();
audioPlayer.stop();
avsQueue.remove(current);
} else if (current instanceof AvsReplaceEnqueuedItem) {
//clear all items
//mAvsItemQueue.clear();
avsQueue.remove(current);
} else if (current instanceof AvsExpectSpeechItem) {
//listen for user input
audioPlayer.stop();
avsQueue.clear();
startListening();
} else if (current instanceof AvsSetVolumeItem) {
//set our volume
setVolume(((AvsSetVolumeItem) current).getVolume());
avsQueue.remove(current);
} else if (current instanceof AvsAdjustVolumeItem) {
//adjust the volume
adjustVolume(((AvsAdjustVolumeItem) current).getAdjustment());
avsQueue.remove(current);
} else if (current instanceof AvsSetMuteItem) {
//mute/unmute the device
setMute(((AvsSetMuteItem) current).isMute());
avsQueue.remove(current);
} else if (current instanceof AvsMediaPlayCommandItem) {
//fake a hardware "play" press
sendMediaButton(this, KeyEvent.KEYCODE_MEDIA_PLAY);
Log.i(TAG, "Media play command issued");
avsQueue.remove(current);
} else if (current instanceof AvsMediaPauseCommandItem) {
//fake a hardware "pause" press
sendMediaButton(this, KeyEvent.KEYCODE_MEDIA_PAUSE);
Log.i(TAG, "Media pause command issued");
avsQueue.remove(current);
} else if (current instanceof AvsMediaNextCommandItem) {
//fake a hardware "next" press
sendMediaButton(this, KeyEvent.KEYCODE_MEDIA_NEXT);
Log.i(TAG, "Media next command issued");
avsQueue.remove(current);
} else if (current instanceof AvsMediaPreviousCommandItem) {
//fake a hardware "previous" press
sendMediaButton(this, KeyEvent.KEYCODE_MEDIA_PREVIOUS);
Log.i(TAG, "Media previous command issued");
avsQueue.remove(current);
} else if (current instanceof AvsResponseException) {
runOnUiThread(new Runnable() {
@Override
public void run() {
new AlertDialog.Builder(BaseActivity.this)
.setTitle("Error")
.setMessage(((AvsResponseException) current).getDirective().getPayload().getCode() + ": " + ((AvsResponseException) current).getDirective().getPayload().getDescription())
.setPositiveButton(android.R.string.ok, null)
.show();
}
});
avsQueue.remove(current);
checkQueue();
}
}
4.AlexaAudioPlayer中playItem()方法根据不同的AvsItem子类类型用MediaPlayer播放语音:
private void play(AvsItem item){
if(isPlaying()){
Log.w(TAG, "Already playing an item, did you mean to play another?");
}
mItem = item;
if(getMediaPlayer().isPlaying()){
//if we're playing, stop playing before we continue
getMediaPlayer().stop();
}
//reset our player
getMediaPlayer().reset();
if(!TextUtils.isEmpty(mItem.getToken()) && mItem.getToken().contains("PausePrompt")){
//a gross work around for a broke pause mp3 coming from Amazon, play the local mp3
try {
AssetFileDescriptor afd = mContext.getAssets().openFd("shhh.mp3");
getMediaPlayer().setDataSource(afd.getFileDescriptor(),afd.getStartOffset(),afd.getLength());
} catch (IOException e) {
e.printStackTrace();
//bubble up our error
bubbleUpError(e);
}
}else if(mItem instanceof AvsPlayRemoteItem){
//cast our item for easy access
AvsPlayRemoteItem playItem = (AvsPlayRemoteItem) item;
try {
//set stream
getMediaPlayer().setAudioStreamType(AudioManager.STREAM_MUSIC);
//play new url
Log.e(TAG, "播放音频流1为:"+playItem.getUrl());
getMediaPlayer().setDataSource(playItem.getUrl());
} catch (IOException e) {
e.printStackTrace();
//bubble up our error
bubbleUpError(e);
}
}else if(mItem instanceof AvsPlayContentItem){
//cast our item for easy access
AvsPlayContentItem playItem = (AvsPlayContentItem) item;
try {
//set stream
getMediaPlayer().setAudioStreamType(AudioManager.STREAM_MUSIC);
//play new url
Log.e(TAG, "播放音频流2为:"+playItem.getUri());
getMediaPlayer().setDataSource(mContext, playItem.getUri());
} catch (IOException e) {
e.printStackTrace();
//bubble up our error
bubbleUpError(e);
} catch (IllegalStateException e){
e.printStackTrace();
//bubble up our error
bubbleUpError(e);
}
}else if(mItem instanceof AvsSpeakItem){
//cast our item for easy access
AvsSpeakItem playItem = (AvsSpeakItem) item;
//write out our raw audio data to a file
File path=new File(mContext.getCacheDir(), System.currentTimeMillis()+".mp3");
FileOutputStream fos = null;
try {
fos = new FileOutputStream(path);
fos.write(playItem.getAudio());
fos.close();
//play our newly-written file
Log.e(TAG, "播放音频流3的长度为:"+playItem.getAudio().length);
Log.e(TAG, "播放音频流3为:"+path.getPath().toString());
getMediaPlayer().setDataSource(path.getPath());
} catch (IOException|IllegalStateException e) {
e.printStackTrace();
//bubble up our error
bubbleUpError(e);
}
}
//prepare our player, this will start once prepared because of mPreparedListener
try {
getMediaPlayer().prepareAsync();
}catch (IllegalStateException e){
bubbleUpError(e);
}
}
以上就是AlexaAndroid关于Alexa的登录验证、语音收集、语音数据流的网络发送、网络响应数据的解析及播放的流程