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