1

I have a controller class which needs to perform various asynchronous operations for each item in a list:

  • for each A
    • play audio and wait for completion
    • wait for t seconds
    • For each sub-part B of A:
      • play audio and wait
      • wait for t2 seconds

I know the audio is complete when my OnCompletionListener.onCompletion method is fired, and I know when the timer is completed when my CountDownTimer.onFinish() method is fired. I've tried returning the listeners:

for (final A a : getAs()) {
   show(a);
   playAudio(a.audioBegin, new MediaPlayer.OnCompletionListener() {
     @Override
     public void onCompletion(MediaPlayer mp) {
       wait(a.secs, new OnFinishListener() {
         @Override
         public void onFinish() {
           if (a.hasSubparts) {
             for (final B b : a.getBs()) {
               playAudio(b.audioBegin, new MediaPlayer.OnCompletionListener() {
               ...
       }
     }
   }
}

But then at the end I can't get out of the inner loop, which makes me think this whole idea doesn't work. I've looked at the Future interface, but that doesn't seem to be what I want either.

I know I could solve this with a bunch of variables to keep track of where I would be in the state flow, but a loop with some framework would be much cleaner. Thanks :)

Shawn Lauzon
  • 6,234
  • 4
  • 35
  • 46
  • You could try making the controller class implement both those interfaces, then call playAudio() on the next A using Iterator#next() once onFinish() is called. – fractalwrench May 14 '16 at 19:56
  • @fractalwrench Thanks, but that still leaves me with the problem of knowing where in the nested loop I currently am. I edited my question to add some more of the complexity, which is that there is more audio to play in each of the sub-parts. So you're right, I could easily implement the interfaces, but there's not a single Iterator#next() to call. – Shawn Lauzon May 14 '16 at 20:08
  • Not a real solution, but I ended up creating a little state machine and holding state within variables. Not the cleanest solution, so it would be neat if someone could propose an alternative. – Shawn Lauzon May 15 '16 at 18:04

1 Answers1

0

This sort of task sounds like a good place for RxJava.

Please look at this post, it shows you how to wrap MediaPlayer to work with Rx.

I would use timer operator to make the delays you are interested in and the From operator to go over your list one by one.

Good Luck

Edit: this is one way to do it with rxJava, however this is a simplified version and once you will understand rxJava better you can make it slightly better, for instance wrap the callback, replace the subject mechanism, handle errors and so on.

So, first of all we will make a flat list of playable objects, I assume A and B objects has a common parent, as A actually has more methods then B I assumes this structure:

private interface B {
    Object audioBegin();
    long getWaitSecs();
}

private interface A extends B {
    boolean hasSubparts();
    List<B> getBs();
}

so in order to make an observable of the playing list we will do this:

    //this will create a flat play list
    List<B> flatPlayList = new ArrayList<>();
    for ( A a : getAs()){
       flatPlayList.add(a);
       if ( a.hasSubparts()){
           for (B b : a.getBs()){
               flatPlayList.add(b);
           }
       }
    }
    //just creating an observable out of it
    Observable<B> playListObservable = Observable.from(flatPlayList);

Now we have a playlist and we need to play it one after the other, but wait for the previous one to complete+wait, for this we will use a subject:

    //this will be used to inform every time an audio has completed playing after a wait
    final PublishSubject<Void> onCompleteSubject = PublishSubject.create();

and to patch all together:

//zip will send the next B on the list every time there was an audio completion
    Observable.zip(playListObservable, onCompleteSubject, new Func2<B, Void, B>() {
        @Override
        public B call(B b, Void aVoid) {
            return b;
        }
    }).subscribe(new Action1<B>() {
        @Override
        public void call(final B b) {
            //play the actual audio
            playAudio(b.audioBegin(), new MediaPlayer.OnCompletionListener() {
                @Override
                public void onCompletion(MediaPlayer mp) {
                    //audio has completed playing, so we will wait sec as needed then infrom that we completed;
                    Observable.timer(b.getWaitSecs(), TimeUnit.SECONDS).subscribe(new Action1<Long>() {
                        @Override
                        public void call(Long aLong) {
                            onCompleteSubject.onNext(null);
                        }
                    });
                }
            });
        }
    });

finally, because the first item won't start playing before there is a complete event, we will just send one to start the whole chain of events:

onCompleteSubject.onNext(null); //will cause to start playing
ndori
  • 1,934
  • 1
  • 18
  • 23
  • Thanks @ndori, I had not heard of RxJava before. I see [this SO post](http://stackoverflow.com/questions/28402376/how-to-compose-observables-to-avoid-the-given-nested-and-dependent-callbacks) on RxJava to solve a problem given [here](https://gist.github.com/benjchristensen/4677544) with the exact "nested callback hell" I'm hitting. I'll investigate more to see if this works. – Shawn Lauzon May 14 '16 at 21:05
  • No, that didn't work. RxJava looks cool, but it's purpose is for listening to multiple events that could finish at different times. I have a different problem: listening to an event within a loop. – Shawn Lauzon May 15 '16 at 18:03
  • added a full solution, RxJava has a steep learning curve, but it worth it. – ndori May 21 '16 at 15:56
  • Wow, thanks @ndori. Impressive. It'll take me some time to get to this, but I sincerely thank you for the code. – Shawn Lauzon May 23 '16 at 06:01
  • you are welcome, please mark it if solves your problem – ndori Jun 15 '16 at 09:20