The logic of the question and answer sequence is another component that has no computer vision functionality. We will encapsulate it in a class called YesNoAudioTree
, which is responsible for playing the next audio clip whenever the app's computer vision component notifies it of a "Yes" or "No" answer.
Remember to download the audio clips and other resources from http://nummist.com/opencv/7376_04_res.zip and extract them to the project's res/raw
folder. However, note that the audio clips in this download are by no means an exhaustive set of questions and guesses about characters in the Bond franchise. Feel free to add your own clips and your own logic for playing them.
Create a file, src/YesNoAudioTree.java
. Our YesNoAudioTree
class needs member variables to store a media player and a related context, an ID for the most recently played audio clip, and information gathered from the answers to the previous questions. Specifically, the next question depends on whether the unknown person is already identified as a member of MI6, the CIA, the KGB, or a criminal organization. This information, along with the answer to the most recent question, will be enough for us to build a simple tree of questions in order to identify several characters from the Bond franchise. The class's implementation begins as follows:
package com.nummist.goldgesture; import android.content.Context; import android.media.MediaPlayer; import android.media.MediaPlayer.OnCompletionListener; public final class YesNoAudioTree { private enum Affiliation { UNKNOWN, MI6, CIA, KGB, CRIMINAL } private int mLastAudioResource; private Affiliation mAffiliation; private Context mContext; private MediaPlayer mMediaPlayer;
The class is instantiated with Context
, which is a standard abstraction of the app's Android environment:
public YesNoAudioTree(final Context context) { mContext = context; }
The context
object is needed later to create a media player.
For more information about the Android SDK's MediaPlayer
class, refer to the official documentation at http://developer.android.com/reference/android/media/MediaPlayer.html.
To (re)start from the first question, we will call a start
method. It resets the data about the person and plays the first audio clip using a private helper method, play
:
public void start() { mAffiliation = Affiliation.UNKNOWN; play(R.raw.intro); }
To stop any current clip and clean up the audio player (for example, when the app pauses or finishes), we will call a stop
method:
public void stop() { if (mMediaPlayer != null) { mMediaPlayer.release(); } }
When the user has answered "Yes" to a question, we will call the takeYesBranch
method. It uses nested switch
statements to pick the next audio clip based on the previous answers and the most recent question:
public void takeYesBranch() { if (mMediaPlayer != null && mMediaPlayer.isPlaying()) { // Do not interrupt the audio that is already playing. return; } switch (mAffiliation) { case UNKNOWN: switch (mLastAudioResource) { case R.raw.q_mi6: mAffiliation = Affiliation.MI6; play(R.raw.q_martinis); break; case R.raw.q_cia: mAffiliation = Affiliation.CIA; play(R.raw.q_bond_friend); break; case R.raw.q_kgb: mAffiliation = Affiliation.KGB; play(R.raw.q_chief); break; case R.raw.q_criminal: mAffiliation = Affiliation.CRIMINAL; play(R.raw.q_chief); break; } break; case MI5: // The person works for MI5. switch (mLastAudioResource) { case R.raw.q_martinis: // The person drinks shaken martinis (007). play(R.raw.win_007); break; // ... // See the code bundle for more cases. // ... default: // The person remains unknown. play(R.raw.lose); break; } break; // ... // See the code bundle for more cases. // ... }
Similarly, when the user has answered "No" to a question, we will call the takeNoBranch
method, which also contains big, nested switch
statements:
public void takeNoBranch() { if (mMediaPlayer != null && mMediaPlayer.isPlaying()) { // Do not interrupt the audio that is already playing. return; } switch (mAffiliation) { case UNKNOWN: switch (mLastAudioResource) { case R.raw.q_mi6: // The person does not work for MI6. // Ask whether the person works for a criminal // organization. play(R.raw.q_criminal); break; // ... // See the code bundle for more cases. // ... default: // The person remains unknown. play(R.raw.lose); break; } break; // ... // See the code bundle for more cases. // ... }
When certain clips finish playing, we want to automatically advance to another clip without requiring a Yes
or No
from the user. A private helper method, takeAutoBranch
, implements the relevant logic in a switch
statement:
private void takeAutoBranch() { switch (mLastAudioResource) { case R.raw.intro: play(R.raw.q_mi6); break; case R.raw.win_007: case R.raw.win_blofeld: case R.raw.win_gogol: case R.raw.win_jaws: case R.raw.win_leiter: case R.raw.win_m: case R.raw.win_moneypenny: case R.raw.win_q: case R.raw.win_rublevitch: case R.raw.win_tanner: case R.raw.lose: start(); break; } }
Whenever we need to play an audio clip, the play
private helper method is called. It creates an instance of MediaPlayer
using the context and an audio clip's ID, which is given to play
as an argument. The audio is played and a callback is set so that the media player will be cleaned up and takeAutoBranch
will be called when the clip is done:
private void play(final int audioResource) { mLastAudioResource = audioResource; mMediaPlayer = MediaPlayer.create(mContext, audioResource); mMediaPlayer.setOnCompletionListener( new OnCompletionListener() { @Override public void onCompletion(final MediaPlayer mediaPlayer) { mediaPlayer.release(); if (mMediaPlayer == mediaPlayer) { mMediaPlayer = null; } takeAutoBranch(); } }); mMediaPlayer.start(); } }
Now that we have written our supporting classes, we are ready to tackle the app's main class, including the computer vision functionality.