• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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.Log;
31 
32 import java.util.ArrayList;
33 import java.util.Collections;
34 import java.util.Comparator;
35 import java.util.HashSet;
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 
getParTimeline( ElementParallelTimeContainer par, double offset, double maxOffset)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 
getSeqTimeline( ElementSequentialTimeContainer seq, double offset, double maxOffset)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 
getTimeline(ElementTime element, double offset, double maxOffset)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 
SmilPlayer()247     private SmilPlayer() {
248         // Private constructor
249     }
250 
getPlayer()251     public static SmilPlayer getPlayer() {
252         if (sPlayer == null) {
253             sPlayer = new SmilPlayer();
254         }
255         return sPlayer;
256     }
257 
isPlayingState()258     public synchronized boolean isPlayingState() {
259         return mState == SmilPlayerState.PLAYING;
260     }
261 
isPlayedState()262     public synchronized boolean isPlayedState() {
263         return mState == SmilPlayerState.PLAYED;
264     }
265 
isPausedState()266     public synchronized boolean isPausedState() {
267         return mState == SmilPlayerState.PAUSED;
268     }
269 
isStoppedState()270     public synchronized boolean isStoppedState() {
271         return mState == SmilPlayerState.STOPPED;
272     }
273 
isPauseAction()274     private synchronized boolean isPauseAction() {
275         return mAction == SmilPlayerAction.PAUSE;
276     }
277 
isStartAction()278     private synchronized boolean isStartAction() {
279         return mAction == SmilPlayerAction.START;
280     }
281 
isStopAction()282     private synchronized boolean isStopAction() {
283         return mAction == SmilPlayerAction.STOP;
284     }
285 
isReloadAction()286     private synchronized boolean isReloadAction() {
287         return mAction == SmilPlayerAction.RELOAD;
288     }
289 
isNextAction()290     private synchronized boolean isNextAction() {
291       return mAction == SmilPlayerAction.NEXT;
292     }
293 
isPrevAction()294     private synchronized boolean isPrevAction() {
295       return mAction == SmilPlayerAction.PREV;
296     }
297 
init(ElementTime root)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 
play()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 
pause()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 
start()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 
stop()340     public synchronized void stop() {
341         if (isPlayingState() || isPausedState()) {
342             mAction = SmilPlayerAction.STOP;
343             notifyAll();
344         } else if (isPlayedState()) {
345             actionStop();
346         }
347     }
348 
stopWhenReload()349     public synchronized void stopWhenReload() {
350         endActiveElements();
351     }
352 
reload()353     public synchronized void reload() {
354         if (isPlayingState() || isPausedState()) {
355             mAction = SmilPlayerAction.RELOAD;
356             notifyAll();
357         } else if (isPlayedState()) {
358             actionReload();
359         }
360     }
361 
next()362     public synchronized void next() {
363       if (isPlayingState() || isPausedState()) {
364         mAction = SmilPlayerAction.NEXT;
365         notifyAll();
366       }
367     }
368 
prev()369     public synchronized void prev() {
370       if (isPlayingState() || isPausedState()) {
371         mAction = SmilPlayerAction.PREV;
372         notifyAll();
373       }
374     }
375 
isBeginOfSlide(TimelineEntry entry)376     private synchronized boolean isBeginOfSlide(TimelineEntry entry) {
377         return (TimelineEntry.ACTION_BEGIN == entry.getAction())
378                     && (entry.getElement() instanceof SmilParElementImpl);
379     }
380 
reloadActiveSlide()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 
beginSmilDocument()392     private synchronized void beginSmilDocument() {
393         TimelineEntry entry = mAllEntries.get(0);
394         actionEntry(entry);
395     }
396 
getOffsetTime(ElementTime element)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 
seekActiveMedia()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 
waitForEntry(long interval)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 
getDuration()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 
getCurrentPosition()462     public synchronized int getCurrentPosition() {
463         return (int) mCurrentTime;
464     }
465 
endActiveElements()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 
pauseActiveElements()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 
resumeActiveElements()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 
waitForWakeUp()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 
actionEntry(TimelineEntry entry)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 
reloadCurrentEntry()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 
stopCurrentSlide()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 
loadNextSlide()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 
loadPrevSlide()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 
actionNext()613     private synchronized TimelineEntry actionNext() {
614         stopCurrentSlide();
615         return loadNextSlide();
616    }
617 
actionPrev()618     private synchronized TimelineEntry actionPrev() {
619         stopCurrentSlide();
620         return loadPrevSlide();
621     }
622 
actionPause()623     private synchronized void actionPause() {
624         pauseActiveElements();
625         mState = SmilPlayerState.PAUSED;
626         mAction = SmilPlayerAction.NO_ACTIVE_ACTION;
627     }
628 
actionStop()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 
actionReload()638     private synchronized void actionReload() {
639         reloadActiveSlide();
640         mAction = SmilPlayerAction.NO_ACTIVE_ACTION;
641     }
642 
run()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 
TimelineEntry(double offsetTime, ElementTime element, int action)731         public TimelineEntry(double offsetTime, ElementTime element, int action) {
732             mOffsetTime = offsetTime;
733             mElement = element;
734             mAction  = action;
735         }
736 
getOffsetTime()737         public double getOffsetTime() {
738             return mOffsetTime;
739         }
740 
getElement()741         public ElementTime getElement() {
742             return mElement;
743         }
744 
getAction()745         public int getAction() {
746             return mAction;
747         }
748 
toString()749         public String toString() {
750             return "Type = " + mElement + " offset = " + getOffsetTime() + " action = " + getAction();
751         }
752     }
753 
dumpAllEntries()754     private void dumpAllEntries() {
755         if (LOCAL_LOGV) {
756             for (TimelineEntry entry : mAllEntries) {
757                 Log.v(TAG, "[Entry] "+ entry);
758             }
759         }
760     }
761 }
762