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