您的当前位置:首页正文

亚马逊语言识别Alexa之AlexaAndroid的接入及AlexaAndroid的源码解析(二)

2024-11-30 来源:个人技术集锦

亚马逊语言识别Alexa之AlexaAndroid的接入及AlexaAndroid的源码解析(二)

上一篇文章介绍了验证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的登录验证、语音收集、语音数据流的网络发送、网络响应数据的解析及播放的流程

显示全文