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