上一篇文章介绍了验证api_key.txt的流程,这首先是开发AlexaAndroid的第一步。在基于AlexaAndroid项目的基础上要分析项目的源码之后,根据的需求去进行下一步的开发。这样就要看懂AlexaAndroid关于语音识别的整个流程。
其实语音识别是亚马逊后台Alexa Service后台在做的,我们只要接入:
1、登录亚马逊的模块;
2、语音收集的模块;
3、把语音以数据流的方式通过网络传入Alexa;
4、Alexa Service后台返回给我的数据后解析成语音播放。
一、亚马逊的登录验证:
首先,在通过网络发送语音数据流到Alexa后台时,需要对账号进行登录验证。以下是以开源项目的源码分析。
登录验证则要跳转到浏览器进行登录验证。在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的登录验证、语音收集、语音数据流的网络发送、网络响应数据的解析及播放的流程