Home | History | Annotate | Download | only in smil
      1 /*
      2  * Copyright (C) 2007-2008 Esmertec AG.
      3  * Copyright (C) 2007-2008 The Android Open Source Project
      4  *
      5  * Licensed under the Apache License, Version 2.0 (the "License");
      6  * you may not use this file except in compliance with the License.
      7  * You may obtain a copy of the License at
      8  *
      9  *      http://www.apache.org/licenses/LICENSE-2.0
     10  *
     11  * Unless required by applicable law or agreed to in writing, software
     12  * distributed under the License is distributed on an "AS IS" BASIS,
     13  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     14  * See the License for the specific language governing permissions and
     15  * limitations under the License.
     16  */
     17 
     18 package com.android.mms.dom.smil;
     19 
     20 import org.w3c.dom.NodeList;
     21 import org.w3c.dom.events.DocumentEvent;
     22 import org.w3c.dom.events.Event;
     23 import org.w3c.dom.events.EventTarget;
     24 import org.w3c.dom.smil.ElementParallelTimeContainer;
     25 import org.w3c.dom.smil.ElementSequentialTimeContainer;
     26 import org.w3c.dom.smil.ElementTime;
     27 import org.w3c.dom.smil.Time;
     28 import org.w3c.dom.smil.TimeList;
     29 
     30 import android.util.Config;
     31 import android.util.Log;
     32 
     33 import java.util.ArrayList;
     34 import java.util.Collections;
     35 import java.util.Comparator;
     36 import java.util.HashSet;
     37 
     38 /**
     39  * The SmilPlayer is responsible for playing, stopping, pausing and resuming a SMIL tree.
     40  * <li>It creates a whole timeline before playing.</li>
     41  * <li>The player runs in a different thread which intends not to block the main thread.</li>
     42  */
     43 public class SmilPlayer implements Runnable {
     44     private static final String TAG = "Mms/smil";
     45     private static final boolean DEBUG = false;
     46     private static final boolean LOCAL_LOGV = DEBUG ? Config.LOGD : Config.LOGV;
     47     private static final int TIMESLICE = 200;
     48 
     49     private static enum SmilPlayerState {
     50         INITIALIZED,
     51         PLAYING,
     52         PLAYED,
     53         PAUSED,
     54         STOPPED,
     55     }
     56 
     57     private static enum SmilPlayerAction {
     58         NO_ACTIVE_ACTION,
     59         RELOAD,
     60         STOP,
     61         PAUSE,
     62         START,
     63         NEXT,
     64         PREV
     65     }
     66 
     67     public static final String MEDIA_TIME_UPDATED_EVENT = "mediaTimeUpdated";
     68 
     69     private static final Comparator<TimelineEntry> sTimelineEntryComparator =
     70         new Comparator<TimelineEntry>() {
     71         public int compare(TimelineEntry o1, TimelineEntry o2) {
     72             return Double.compare(o1.getOffsetTime(), o2.getOffsetTime());
     73         }
     74     };
     75 
     76     private static SmilPlayer sPlayer;
     77 
     78     private long mCurrentTime;
     79     private int mCurrentElement;
     80     private int mCurrentSlide;
     81     private ArrayList<TimelineEntry> mAllEntries;
     82     private ElementTime mRoot;
     83     private Thread mPlayerThread;
     84     private SmilPlayerState mState = SmilPlayerState.INITIALIZED;
     85     private SmilPlayerAction mAction = SmilPlayerAction.NO_ACTIVE_ACTION;
     86     private ArrayList<ElementTime> mActiveElements;
     87     private Event mMediaTimeUpdatedEvent;
     88 
     89     private static ArrayList<TimelineEntry> getParTimeline(
     90             ElementParallelTimeContainer par, double offset, double maxOffset) {
     91         ArrayList<TimelineEntry> timeline = new ArrayList<TimelineEntry>();
     92 
     93         // Set my begin at first
     94         TimeList myBeginList = par.getBegin();
     95         /*
     96          * Begin list only contain 1 begin time which has been resolved.
     97          * @see com.android.mms.dom.smil.ElementParallelTimeContainerImpl#getBegin()
     98          */
     99         Time begin = myBeginList.item(0);
    100         double beginOffset = begin.getResolvedOffset() + offset;
    101         if (beginOffset > maxOffset) {
    102             // This element can't be started.
    103             return timeline;
    104         }
    105         TimelineEntry myBegin = new TimelineEntry(beginOffset, par, TimelineEntry.ACTION_BEGIN);
    106         timeline.add(myBegin);
    107 
    108         TimeList myEndList = par.getEnd();
    109         /*
    110          * End list only contain 1 end time which has been resolved.
    111          * @see com.android.mms.dom.smil.ElementParallelTimeContainerImpl#getEnd()
    112          */
    113         Time end = myEndList.item(0);
    114         double endOffset = end.getResolvedOffset() + offset;
    115         if (endOffset > maxOffset) {
    116             endOffset = maxOffset;
    117         }
    118         TimelineEntry myEnd = new TimelineEntry(endOffset, par, TimelineEntry.ACTION_END);
    119 
    120         maxOffset = endOffset;
    121 
    122         NodeList children = par.getTimeChildren();
    123         for (int i = 0; i < children.getLength(); ++i) {
    124             ElementTime child = (ElementTime) children.item(i);
    125             ArrayList<TimelineEntry> childTimeline = getTimeline(child, offset, maxOffset);
    126             timeline.addAll(childTimeline);
    127         }
    128 
    129         Collections.sort(timeline, sTimelineEntryComparator);
    130 
    131         // Add end-event to timeline for all active children
    132         NodeList activeChildrenAtEnd = par.getActiveChildrenAt(
    133                 (float) (endOffset - offset) * 1000);
    134         for (int i = 0; i < activeChildrenAtEnd.getLength(); ++i) {
    135             timeline.add(new TimelineEntry(endOffset,
    136                     (ElementTime) activeChildrenAtEnd.item(i),
    137                     TimelineEntry.ACTION_END));
    138         }
    139 
    140         // Set my end at last
    141         timeline.add(myEnd);
    142 
    143         return timeline;
    144     }
    145 
    146     private static ArrayList<TimelineEntry> getSeqTimeline(
    147             ElementSequentialTimeContainer seq, double offset, double maxOffset) {
    148         ArrayList<TimelineEntry> timeline = new ArrayList<TimelineEntry>();
    149         double orgOffset = offset;
    150 
    151         // Set my begin at first
    152         TimeList myBeginList = seq.getBegin();
    153         /*
    154          * Begin list only contain 1 begin time which has been resolved.
    155          * @see com.android.mms.dom.smil.ElementSequentialTimeContainerImpl#getBegin()
    156          */
    157         Time begin = myBeginList.item(0);
    158         double beginOffset = begin.getResolvedOffset() + offset;
    159         if (beginOffset > maxOffset) {
    160             // This element can't be started.
    161             return timeline;
    162         }
    163         TimelineEntry myBegin = new TimelineEntry(beginOffset, seq, TimelineEntry.ACTION_BEGIN);
    164         timeline.add(myBegin);
    165 
    166         TimeList myEndList = seq.getEnd();
    167         /*
    168          * End list only contain 1 end time which has been resolved.
    169          * @see com.android.mms.dom.smil.ElementSequentialTimeContainerImpl#getEnd()
    170          */
    171         Time end = myEndList.item(0);
    172         double endOffset = end.getResolvedOffset() + offset;
    173         if (endOffset > maxOffset) {
    174             endOffset = maxOffset;
    175         }
    176         TimelineEntry myEnd = new TimelineEntry(endOffset, seq, TimelineEntry.ACTION_END);
    177 
    178         maxOffset = endOffset;
    179 
    180         // Get children's timelines
    181         NodeList children = seq.getTimeChildren();
    182         for (int i = 0; i < children.getLength(); ++i) {
    183             ElementTime child = (ElementTime) children.item(i);
    184             ArrayList<TimelineEntry> childTimeline = getTimeline(child, offset, maxOffset);
    185             timeline.addAll(childTimeline);
    186 
    187             // Since the child timeline has been sorted, the offset of the last one is the biggest.
    188             offset = childTimeline.get(childTimeline.size() - 1).getOffsetTime();
    189         }
    190 
    191         // Add end-event to timeline for all active children
    192         NodeList activeChildrenAtEnd = seq.getActiveChildrenAt(
    193                 (float) (endOffset - orgOffset));
    194         for (int i = 0; i < activeChildrenAtEnd.getLength(); ++i) {
    195             timeline.add(new TimelineEntry(endOffset,
    196                     (ElementTime) activeChildrenAtEnd.item(i),
    197                     TimelineEntry.ACTION_END));
    198         }
    199 
    200         // Set my end at last
    201         timeline.add(myEnd);
    202 
    203         return timeline;
    204     }
    205 
    206     private static ArrayList<TimelineEntry> getTimeline(ElementTime element,
    207             double offset, double maxOffset) {
    208         if (element instanceof ElementParallelTimeContainer) {
    209             return getParTimeline((ElementParallelTimeContainer) element, offset, maxOffset);
    210         } else if (element instanceof ElementSequentialTimeContainer) {
    211             return getSeqTimeline((ElementSequentialTimeContainer) element, offset, maxOffset);
    212         } else {
    213             // Not ElementTimeContainer here
    214             ArrayList<TimelineEntry> timeline = new ArrayList<TimelineEntry>();
    215 
    216             TimeList beginList = element.getBegin();
    217             for (int i = 0; i < beginList.getLength(); ++i) {
    218                 Time begin = beginList.item(i);
    219                 if (begin.getResolved()) {
    220                     double beginOffset = begin.getResolvedOffset() + offset;
    221                     if (beginOffset <= maxOffset) {
    222                         TimelineEntry entry = new TimelineEntry(beginOffset,
    223                                 element, TimelineEntry.ACTION_BEGIN);
    224                         timeline.add(entry);
    225                     }
    226                 }
    227             }
    228 
    229             TimeList endList = element.getEnd();
    230             for (int i = 0; i < endList.getLength(); ++i) {
    231                 Time end = endList.item(i);
    232                 if (end.getResolved()) {
    233                     double endOffset = end.getResolvedOffset() + offset;
    234                     if (endOffset <= maxOffset) {
    235                         TimelineEntry entry = new TimelineEntry(endOffset,
    236                                 element, TimelineEntry.ACTION_END);
    237                         timeline.add(entry);
    238                     }
    239                 }
    240             }
    241 
    242             Collections.sort(timeline, sTimelineEntryComparator);
    243 
    244             return timeline;
    245         }
    246     }
    247 
    248     private SmilPlayer() {
    249         // Private constructor
    250     }
    251 
    252     public static SmilPlayer getPlayer() {
    253         if (sPlayer == null) {
    254             sPlayer = new SmilPlayer();
    255         }
    256         return sPlayer;
    257     }
    258 
    259     public synchronized boolean isPlayingState() {
    260         return mState == SmilPlayerState.PLAYING;
    261     }
    262 
    263     public synchronized boolean isPlayedState() {
    264         return mState == SmilPlayerState.PLAYED;
    265     }
    266 
    267     public synchronized boolean isPausedState() {
    268         return mState == SmilPlayerState.PAUSED;
    269     }
    270 
    271     public synchronized boolean isStoppedState() {
    272         return mState == SmilPlayerState.STOPPED;
    273     }
    274 
    275     private synchronized boolean isPauseAction() {
    276         return mAction == SmilPlayerAction.PAUSE;
    277     }
    278 
    279     private synchronized boolean isStartAction() {
    280         return mAction == SmilPlayerAction.START;
    281     }
    282 
    283     private synchronized boolean isStopAction() {
    284         return mAction == SmilPlayerAction.STOP;
    285     }
    286 
    287     private synchronized boolean isReloadAction() {
    288         return mAction == SmilPlayerAction.RELOAD;
    289     }
    290 
    291     private synchronized boolean isNextAction() {
    292       return mAction == SmilPlayerAction.NEXT;
    293     }
    294 
    295     private synchronized boolean isPrevAction() {
    296       return mAction == SmilPlayerAction.PREV;
    297     }
    298 
    299     public synchronized void init(ElementTime root) {
    300         mRoot = root;
    301         mAllEntries = getTimeline(mRoot, 0, Long.MAX_VALUE);
    302         mMediaTimeUpdatedEvent = ((DocumentEvent) mRoot).createEvent("Event");
    303         mMediaTimeUpdatedEvent.initEvent(MEDIA_TIME_UPDATED_EVENT, false, false);
    304         mActiveElements = new ArrayList<ElementTime>();
    305     }
    306 
    307     public synchronized void play() {
    308         if (!isPlayingState()) {
    309             mCurrentTime = 0;
    310             mCurrentElement = 0;
    311             mCurrentSlide = 0;
    312             mPlayerThread = new Thread(this);
    313             mState = SmilPlayerState.PLAYING;
    314             mPlayerThread.start();
    315         } else {
    316             Log.w(TAG, "Error State: Playback is playing!");
    317         }
    318     }
    319 
    320     public synchronized void pause() {
    321         if (isPlayingState()) {
    322             mAction = SmilPlayerAction.PAUSE;
    323             notifyAll();
    324         } else {
    325             Log.w(TAG, "Error State: Playback is not playing!");
    326         }
    327     }
    328 
    329     public synchronized void start() {
    330         if (isPausedState()) {
    331             resumeActiveElements();
    332             mAction = SmilPlayerAction.START;
    333             notifyAll();
    334         } else if (isPlayedState()) {
    335             play();
    336         } else {
    337             Log.w(TAG, "Error State: Playback can not be started!");
    338         }
    339     }
    340 
    341     public synchronized void stop() {
    342         if (isPlayingState() || isPausedState()) {
    343             mAction = SmilPlayerAction.STOP;
    344             notifyAll();
    345         } else if (isPlayedState()) {
    346             actionStop();
    347         }
    348     }
    349 
    350     public synchronized void stopWhenReload() {
    351         endActiveElements();
    352     }
    353 
    354     public synchronized void reload() {
    355         if (isPlayingState() || isPausedState()) {
    356             mAction = SmilPlayerAction.RELOAD;
    357             notifyAll();
    358         } else if (isPlayedState()) {
    359             actionReload();
    360         }
    361     }
    362 
    363     public synchronized void next() {
    364       if (isPlayingState() || isPausedState()) {
    365         mAction = SmilPlayerAction.NEXT;
    366         notifyAll();
    367       }
    368     }
    369 
    370     public synchronized void prev() {
    371       if (isPlayingState() || isPausedState()) {
    372         mAction = SmilPlayerAction.PREV;
    373         notifyAll();
    374       }
    375     }
    376 
    377     private synchronized boolean isBeginOfSlide(TimelineEntry entry) {
    378         return (TimelineEntry.ACTION_BEGIN == entry.getAction())
    379                     && (entry.getElement() instanceof SmilParElementImpl);
    380     }
    381 
    382     private synchronized void reloadActiveSlide() {
    383         mActiveElements.clear();
    384         beginSmilDocument();
    385 
    386         for (int i = mCurrentSlide; i < mCurrentElement; i++) {
    387             TimelineEntry entry = mAllEntries.get(i);
    388             actionEntry(entry);
    389         }
    390         seekActiveMedia();
    391     }
    392 
    393     private synchronized void beginSmilDocument() {
    394         TimelineEntry entry = mAllEntries.get(0);
    395         actionEntry(entry);
    396     }
    397 
    398     private synchronized double getOffsetTime(ElementTime element) {
    399         for (int i = mCurrentSlide; i < mCurrentElement; i++) {
    400             TimelineEntry entry = mAllEntries.get(i);
    401             if (element.equals(entry.getElement())) {
    402                 return entry.getOffsetTime() * 1000;  // in ms
    403             }
    404         }
    405         return -1;
    406     }
    407 
    408     private synchronized void seekActiveMedia() {
    409         for (int i = mActiveElements.size() - 1; i >= 0; i--) {
    410             ElementTime element = mActiveElements.get(i);
    411             if (element instanceof SmilParElementImpl) {
    412                 return;
    413             }
    414             double offset = getOffsetTime(element);
    415             if ((offset >= 0) && (offset <= mCurrentTime)) {
    416                 if (LOCAL_LOGV) {
    417                     Log.v(TAG, "[SEEK]  " + " at " + mCurrentTime
    418                             + " " + element);
    419                 }
    420                 element.seekElement( (float) (mCurrentTime - offset) );
    421             }
    422         }
    423     }
    424 
    425     private synchronized void waitForEntry(long interval)
    426             throws InterruptedException {
    427         if (LOCAL_LOGV) {
    428             Log.v(TAG, "Waiting for " + interval + "ms.");
    429         }
    430 
    431         long overhead = 0;
    432 
    433         while (interval > 0) {
    434             long startAt = System.currentTimeMillis();
    435             long sleep = Math.min(interval, TIMESLICE);
    436             if (overhead < sleep) {
    437                 wait(sleep - overhead);
    438                 mCurrentTime += sleep;
    439             } else {
    440                 sleep = 0;
    441                 mCurrentTime += overhead;
    442             }
    443 
    444             if (isStopAction() || isReloadAction() || isPauseAction() || isNextAction() ||
    445                 isPrevAction()) {
    446                 return;
    447             }
    448 
    449             ((EventTarget) mRoot).dispatchEvent(mMediaTimeUpdatedEvent);
    450 
    451             interval -= TIMESLICE;
    452             overhead = System.currentTimeMillis() - startAt - sleep;
    453         }
    454     }
    455 
    456     public synchronized int getDuration() {
    457          if ((mAllEntries != null) && !mAllEntries.isEmpty()) {
    458              return (int) mAllEntries.get(mAllEntries.size() - 1).mOffsetTime * 1000;
    459          }
    460          return 0;
    461     }
    462 
    463     public synchronized int getCurrentPosition() {
    464         return (int) mCurrentTime;
    465     }
    466 
    467     private synchronized void endActiveElements() {
    468         for (int i = mActiveElements.size() - 1; i >= 0; i--) {
    469             ElementTime element = mActiveElements.get(i);
    470             if (LOCAL_LOGV) {
    471                 Log.v(TAG, "[STOP]  " + " at " + mCurrentTime
    472                         + " " + element);
    473             }
    474             element.endElement();
    475         }
    476     }
    477 
    478     private synchronized void pauseActiveElements() {
    479         for (int i = mActiveElements.size() - 1; i >= 0; i--) {
    480             ElementTime element = mActiveElements.get(i);
    481             if (LOCAL_LOGV) {
    482                 Log.v(TAG, "[PAUSE]  " + " at " + mCurrentTime
    483                         + " " + element);
    484             }
    485             element.pauseElement();
    486         }
    487     }
    488 
    489     private synchronized void resumeActiveElements() {
    490         int size = mActiveElements.size();
    491         for (int i = 0; i < size; i++) {
    492             ElementTime element = mActiveElements.get(i);
    493             if (LOCAL_LOGV) {
    494                 Log.v(TAG, "[RESUME]  " + " at " + mCurrentTime
    495                         + " " + element);
    496             }
    497             element.resumeElement();
    498         }
    499     }
    500 
    501     private synchronized void waitForWakeUp() {
    502         try {
    503             while ( !(isStartAction() || isStopAction() || isReloadAction() ||
    504                     isNextAction() || isPrevAction()) ) {
    505                 wait(TIMESLICE);
    506             }
    507             if (isStartAction()) {
    508                 mAction = SmilPlayerAction.NO_ACTIVE_ACTION;
    509                 mState = SmilPlayerState.PLAYING;
    510             }
    511         } catch (InterruptedException e) {
    512             Log.e(TAG, "Unexpected InterruptedException.", e);
    513         }
    514     }
    515 
    516     private synchronized void actionEntry(TimelineEntry entry) {
    517         switch (entry.getAction()) {
    518             case TimelineEntry.ACTION_BEGIN:
    519                 if (LOCAL_LOGV) {
    520                     Log.v(TAG, "[START] " + " at " + mCurrentTime + " "
    521                             + entry.getElement());
    522                 }
    523                 entry.getElement().beginElement();
    524                 mActiveElements.add(entry.getElement());
    525                 break;
    526             case TimelineEntry.ACTION_END:
    527                 if (LOCAL_LOGV) {
    528                     Log.v(TAG, "[STOP]  " + " at " + mCurrentTime + " "
    529                             + entry.getElement());
    530                 }
    531                 entry.getElement().endElement();
    532                 mActiveElements.remove(entry.getElement());
    533                 break;
    534             default:
    535                 break;
    536         }
    537     }
    538 
    539     private synchronized TimelineEntry reloadCurrentEntry() {
    540         // Check if the position is less than size of all entries
    541         if (mCurrentElement < mAllEntries.size()) {
    542             return mAllEntries.get(mCurrentElement);
    543         } else {
    544             return null;
    545         }
    546     }
    547 
    548     private void stopCurrentSlide() {
    549         HashSet<TimelineEntry> skippedEntries = new HashSet<TimelineEntry>();
    550         int totalEntries = mAllEntries.size();
    551         for (int i = mCurrentElement; i < totalEntries; i++) {
    552             // Stop any started entries, and skip the not started entries until
    553             // meeting the end of slide
    554             TimelineEntry entry = mAllEntries.get(i);
    555             int action = entry.getAction();
    556             if (entry.getElement() instanceof SmilParElementImpl &&
    557                     action == TimelineEntry.ACTION_END) {
    558                 actionEntry(entry);
    559                 mCurrentElement = i;
    560                 break;
    561             } else if (action == TimelineEntry.ACTION_END && !skippedEntries.contains(entry)) {
    562                     actionEntry(entry);
    563             } else if (action == TimelineEntry.ACTION_BEGIN) {
    564                 skippedEntries.add(entry);
    565             }
    566         }
    567     }
    568 
    569     private TimelineEntry loadNextSlide() {
    570       TimelineEntry entry;
    571       int totalEntries = mAllEntries.size();
    572       for (int i = mCurrentElement; i < totalEntries; i++) {
    573           entry = mAllEntries.get(i);
    574           if (isBeginOfSlide(entry)) {
    575               mCurrentElement = i;
    576               mCurrentSlide = i;
    577               mCurrentTime = (long)(entry.getOffsetTime() * 1000);
    578               return entry;
    579           }
    580       }
    581       // No slide, finish play back
    582       mCurrentElement++;
    583       entry = null;
    584       if (mCurrentElement < totalEntries) {
    585           entry = mAllEntries.get(mCurrentElement);
    586           mCurrentTime = (long)(entry.getOffsetTime() * 1000);
    587       }
    588       return entry;
    589     }
    590 
    591     private TimelineEntry loadPrevSlide() {
    592       int skippedSlides = 1;
    593       int latestBeginEntryIndex = -1;
    594       for (int i = mCurrentSlide; i >= 0; i--) {
    595         TimelineEntry entry = mAllEntries.get(i);
    596         if (isBeginOfSlide(entry)) {
    597             latestBeginEntryIndex = i;
    598           if (0 == skippedSlides-- ) {
    599             mCurrentElement = i;
    600             mCurrentSlide = i;
    601             mCurrentTime = (long)(entry.getOffsetTime() * 1000);
    602             return entry;
    603           }
    604         }
    605       }
    606       if (latestBeginEntryIndex != -1) {
    607           mCurrentElement = latestBeginEntryIndex;
    608           mCurrentSlide = latestBeginEntryIndex;
    609           return mAllEntries.get(mCurrentElement);
    610       }
    611       return null;
    612     }
    613 
    614     private synchronized TimelineEntry actionNext() {
    615         stopCurrentSlide();
    616         return loadNextSlide();
    617    }
    618 
    619     private synchronized TimelineEntry actionPrev() {
    620         stopCurrentSlide();
    621         return loadPrevSlide();
    622     }
    623 
    624     private synchronized void actionPause() {
    625         pauseActiveElements();
    626         mState = SmilPlayerState.PAUSED;
    627         mAction = SmilPlayerAction.NO_ACTIVE_ACTION;
    628     }
    629 
    630     private synchronized void actionStop() {
    631         endActiveElements();
    632         mCurrentTime = 0;
    633         mCurrentElement = 0;
    634         mCurrentSlide = 0;
    635         mState = SmilPlayerState.STOPPED;
    636         mAction = SmilPlayerAction.NO_ACTIVE_ACTION;
    637     }
    638 
    639     private synchronized void actionReload() {
    640         reloadActiveSlide();
    641         mAction = SmilPlayerAction.NO_ACTIVE_ACTION;
    642     }
    643 
    644     public void run() {
    645         if (isStoppedState()) {
    646             return;
    647         }
    648         if (LOCAL_LOGV) {
    649             dumpAllEntries();
    650         }
    651         // Play the Element by following the timeline
    652         int size = mAllEntries.size();
    653         for (mCurrentElement = 0; mCurrentElement < size; mCurrentElement++) {
    654             TimelineEntry entry = mAllEntries.get(mCurrentElement);
    655             if (isBeginOfSlide(entry)) {
    656                 mCurrentSlide = mCurrentElement;
    657             }
    658             long offset = (long) (entry.getOffsetTime() * 1000); // in ms.
    659             while (offset > mCurrentTime) {
    660                 try {
    661                     waitForEntry(offset - mCurrentTime);
    662                 } catch (InterruptedException e) {
    663                     Log.e(TAG, "Unexpected InterruptedException.", e);
    664                 }
    665 
    666                 while (isPauseAction() || isStopAction() || isReloadAction() || isNextAction() ||
    667                     isPrevAction()) {
    668                     if (isPauseAction()) {
    669                         actionPause();
    670                         waitForWakeUp();
    671                     }
    672 
    673                     if (isStopAction()) {
    674                         actionStop();
    675                         return;
    676                     }
    677 
    678                     if (isReloadAction()) {
    679                         actionReload();
    680                         entry = reloadCurrentEntry();
    681                         if (entry == null)
    682                             return;
    683                         if (isPausedState()) {
    684                             mAction = SmilPlayerAction.PAUSE;
    685                         }
    686                     }
    687 
    688                     if (isNextAction()) {
    689                         TimelineEntry nextEntry = actionNext();
    690                         if (nextEntry != null) {
    691                             entry = nextEntry;
    692                         }
    693                         if (mState == SmilPlayerState.PAUSED) {
    694                             mAction = SmilPlayerAction.PAUSE;
    695                             actionEntry(entry);
    696                         } else {
    697                             mAction = SmilPlayerAction.NO_ACTIVE_ACTION;
    698                         }
    699                         offset = mCurrentTime;
    700                     }
    701 
    702                     if (isPrevAction()) {
    703                         TimelineEntry prevEntry = actionPrev();
    704                         if (prevEntry != null) {
    705                             entry = prevEntry;
    706                         }
    707                         if (mState == SmilPlayerState.PAUSED) {
    708                             mAction = SmilPlayerAction.PAUSE;
    709                             actionEntry(entry);
    710                         } else {
    711                             mAction = SmilPlayerAction.NO_ACTIVE_ACTION;
    712                         }
    713                         offset = mCurrentTime;
    714                     }
    715                 }
    716             }
    717             mCurrentTime = offset;
    718             actionEntry(entry);
    719         }
    720 
    721         mState = SmilPlayerState.PLAYED;
    722     }
    723 
    724     private static final class TimelineEntry {
    725         final static int ACTION_BEGIN = 0;
    726         final static int ACTION_END   = 1;
    727 
    728         private final double mOffsetTime;
    729         private final ElementTime mElement;
    730         private final int mAction;
    731 
    732         public TimelineEntry(double offsetTime, ElementTime element, int action) {
    733             mOffsetTime = offsetTime;
    734             mElement = element;
    735             mAction  = action;
    736         }
    737 
    738         public double getOffsetTime() {
    739             return mOffsetTime;
    740         }
    741 
    742         public ElementTime getElement() {
    743             return mElement;
    744         }
    745 
    746         public int getAction() {
    747             return mAction;
    748         }
    749 
    750         public String toString() {
    751             return "Type = " + mElement + " offset = " + getOffsetTime() + " action = " + getAction();
    752         }
    753     }
    754 
    755     private void dumpAllEntries() {
    756         if (LOCAL_LOGV) {
    757             for (TimelineEntry entry : mAllEntries) {
    758                 Log.v(TAG, "[Entry] "+ entry);
    759             }
    760         }
    761     }
    762 }
    763