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