• 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 
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 = DEBUG ? Config.LOGD : Config.LOGV;
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     }
63 
64     public static final String MEDIA_TIME_UPDATED_EVENT = "mediaTimeUpdated";
65 
66     private static final Comparator<TimelineEntry> sTimelineEntryComparator =
67         new Comparator<TimelineEntry>() {
68         public int compare(TimelineEntry o1, TimelineEntry o2) {
69             return Double.compare(o1.getOffsetTime(), o2.getOffsetTime());
70         }
71     };
72 
73     private static SmilPlayer sPlayer;
74 
75     private long mCurrentTime;
76     private int mCurrentElement;
77     private int mCurrentSlide;
78     private ArrayList<TimelineEntry> mAllEntries;
79     private ElementTime mRoot;
80     private Thread mPlayerThread;
81     private SmilPlayerState mState = SmilPlayerState.INITIALIZED;
82     private SmilPlayerAction mAction = SmilPlayerAction.NO_ACTIVE_ACTION;
83     private ArrayList<ElementTime> mActiveElements;
84     private Event mMediaTimeUpdatedEvent;
85 
getParTimeline( ElementParallelTimeContainer par, double offset, double maxOffset)86     private static ArrayList<TimelineEntry> getParTimeline(
87             ElementParallelTimeContainer par, double offset, double maxOffset) {
88         ArrayList<TimelineEntry> timeline = new ArrayList<TimelineEntry>();
89 
90         // Set my begin at first
91         TimeList myBeginList = par.getBegin();
92         /*
93          * Begin list only contain 1 begin time which has been resolved.
94          * @see com.android.mms.dom.smil.ElementParallelTimeContainerImpl#getBegin()
95          */
96         Time begin = myBeginList.item(0);
97         double beginOffset = begin.getResolvedOffset() + offset;
98         if (beginOffset > maxOffset) {
99             // This element can't be started.
100             return timeline;
101         }
102         TimelineEntry myBegin = new TimelineEntry(beginOffset, par, TimelineEntry.ACTION_BEGIN);
103         timeline.add(myBegin);
104 
105         TimeList myEndList = par.getEnd();
106         /*
107          * End list only contain 1 end time which has been resolved.
108          * @see com.android.mms.dom.smil.ElementParallelTimeContainerImpl#getEnd()
109          */
110         Time end = myEndList.item(0);
111         double endOffset = end.getResolvedOffset() + offset;
112         if (endOffset > maxOffset) {
113             endOffset = maxOffset;
114         }
115         TimelineEntry myEnd = new TimelineEntry(endOffset, par, TimelineEntry.ACTION_END);
116 
117         maxOffset = endOffset;
118 
119         NodeList children = par.getTimeChildren();
120         for (int i = 0; i < children.getLength(); ++i) {
121             ElementTime child = (ElementTime) children.item(i);
122             ArrayList<TimelineEntry> childTimeline = getTimeline(child, offset, maxOffset);
123             timeline.addAll(childTimeline);
124         }
125 
126         Collections.sort(timeline, sTimelineEntryComparator);
127 
128         // Add end-event to timeline for all active children
129         NodeList activeChildrenAtEnd = par.getActiveChildrenAt(
130                 (float) (endOffset - offset) * 1000);
131         for (int i = 0; i < activeChildrenAtEnd.getLength(); ++i) {
132             timeline.add(new TimelineEntry(endOffset,
133                     (ElementTime) activeChildrenAtEnd.item(i),
134                     TimelineEntry.ACTION_END));
135         }
136 
137         // Set my end at last
138         timeline.add(myEnd);
139 
140         return timeline;
141     }
142 
getSeqTimeline( ElementSequentialTimeContainer seq, double offset, double maxOffset)143     private static ArrayList<TimelineEntry> getSeqTimeline(
144             ElementSequentialTimeContainer seq, double offset, double maxOffset) {
145         ArrayList<TimelineEntry> timeline = new ArrayList<TimelineEntry>();
146         double orgOffset = offset;
147 
148         // Set my begin at first
149         TimeList myBeginList = seq.getBegin();
150         /*
151          * Begin list only contain 1 begin time which has been resolved.
152          * @see com.android.mms.dom.smil.ElementSequentialTimeContainerImpl#getBegin()
153          */
154         Time begin = myBeginList.item(0);
155         double beginOffset = begin.getResolvedOffset() + offset;
156         if (beginOffset > maxOffset) {
157             // This element can't be started.
158             return timeline;
159         }
160         TimelineEntry myBegin = new TimelineEntry(beginOffset, seq, TimelineEntry.ACTION_BEGIN);
161         timeline.add(myBegin);
162 
163         TimeList myEndList = seq.getEnd();
164         /*
165          * End list only contain 1 end time which has been resolved.
166          * @see com.android.mms.dom.smil.ElementSequentialTimeContainerImpl#getEnd()
167          */
168         Time end = myEndList.item(0);
169         double endOffset = end.getResolvedOffset() + offset;
170         if (endOffset > maxOffset) {
171             endOffset = maxOffset;
172         }
173         TimelineEntry myEnd = new TimelineEntry(endOffset, seq, TimelineEntry.ACTION_END);
174 
175         maxOffset = endOffset;
176 
177         // Get children's timelines
178         NodeList children = seq.getTimeChildren();
179         for (int i = 0; i < children.getLength(); ++i) {
180             ElementTime child = (ElementTime) children.item(i);
181             ArrayList<TimelineEntry> childTimeline = getTimeline(child, offset, maxOffset);
182             timeline.addAll(childTimeline);
183 
184             // Since the child timeline has been sorted, the offset of the last one is the biggest.
185             offset = childTimeline.get(childTimeline.size() - 1).getOffsetTime();
186         }
187 
188         // Add end-event to timeline for all active children
189         NodeList activeChildrenAtEnd = seq.getActiveChildrenAt(
190                 (float) (endOffset - orgOffset));
191         for (int i = 0; i < activeChildrenAtEnd.getLength(); ++i) {
192             timeline.add(new TimelineEntry(endOffset,
193                     (ElementTime) activeChildrenAtEnd.item(i),
194                     TimelineEntry.ACTION_END));
195         }
196 
197         // Set my end at last
198         timeline.add(myEnd);
199 
200         return timeline;
201     }
202 
getTimeline(ElementTime element, double offset, double maxOffset)203     private static ArrayList<TimelineEntry> getTimeline(ElementTime element,
204             double offset, double maxOffset) {
205         if (element instanceof ElementParallelTimeContainer) {
206             return getParTimeline((ElementParallelTimeContainer) element, offset, maxOffset);
207         } else if (element instanceof ElementSequentialTimeContainer) {
208             return getSeqTimeline((ElementSequentialTimeContainer) element, offset, maxOffset);
209         } else {
210             // Not ElementTimeContainer here
211             ArrayList<TimelineEntry> timeline = new ArrayList<TimelineEntry>();
212 
213             TimeList beginList = element.getBegin();
214             for (int i = 0; i < beginList.getLength(); ++i) {
215                 Time begin = beginList.item(i);
216                 if (begin.getResolved()) {
217                     double beginOffset = begin.getResolvedOffset() + offset;
218                     if (beginOffset <= maxOffset) {
219                         TimelineEntry entry = new TimelineEntry(beginOffset,
220                                 element, TimelineEntry.ACTION_BEGIN);
221                         timeline.add(entry);
222                     }
223                 }
224             }
225 
226             TimeList endList = element.getEnd();
227             for (int i = 0; i < endList.getLength(); ++i) {
228                 Time end = endList.item(i);
229                 if (end.getResolved()) {
230                     double endOffset = end.getResolvedOffset() + offset;
231                     if (endOffset <= maxOffset) {
232                         TimelineEntry entry = new TimelineEntry(endOffset,
233                                 element, TimelineEntry.ACTION_END);
234                         timeline.add(entry);
235                     }
236                 }
237             }
238 
239             Collections.sort(timeline, sTimelineEntryComparator);
240 
241             return timeline;
242         }
243     }
244 
SmilPlayer()245     private SmilPlayer() {
246         // Private constructor
247     }
248 
getPlayer()249     public static SmilPlayer getPlayer() {
250         if (sPlayer == null) {
251             sPlayer = new SmilPlayer();
252         }
253         return sPlayer;
254     }
255 
isPlayingState()256     public synchronized boolean isPlayingState() {
257         return mState == SmilPlayerState.PLAYING;
258     }
259 
isPlayedState()260     public synchronized boolean isPlayedState() {
261         return mState == SmilPlayerState.PLAYED;
262     }
263 
isPausedState()264     public synchronized boolean isPausedState() {
265         return mState == SmilPlayerState.PAUSED;
266     }
267 
isStoppedState()268     public synchronized boolean isStoppedState() {
269         return mState == SmilPlayerState.STOPPED;
270     }
271 
isPauseAction()272     private synchronized boolean isPauseAction() {
273         return mAction == SmilPlayerAction.PAUSE;
274     }
275 
isStartAction()276     private synchronized boolean isStartAction() {
277         return mAction == SmilPlayerAction.START;
278     }
279 
isStopAction()280     private synchronized boolean isStopAction() {
281         return mAction == SmilPlayerAction.STOP;
282     }
283 
isReloadAction()284     private synchronized boolean isReloadAction() {
285         return mAction == SmilPlayerAction.RELOAD;
286     }
287 
init(ElementTime root)288     public synchronized void init(ElementTime root) {
289         mRoot = root;
290         mAllEntries = getTimeline(mRoot, 0, Long.MAX_VALUE);
291         mMediaTimeUpdatedEvent = ((DocumentEvent) mRoot).createEvent("Event");
292         mMediaTimeUpdatedEvent.initEvent(MEDIA_TIME_UPDATED_EVENT, false, false);
293         mActiveElements = new ArrayList<ElementTime>();
294     }
295 
play()296     public synchronized void play() {
297         if (!isPlayingState()) {
298             mCurrentTime = 0;
299             mCurrentElement = 0;
300             mCurrentSlide = 0;
301             mPlayerThread = new Thread(this);
302             mState = SmilPlayerState.PLAYING;
303             mPlayerThread.start();
304         } else {
305             Log.w(TAG, "Error State: Playback is playing!");
306         }
307     }
308 
pause()309     public synchronized void pause() {
310         if (isPlayingState()) {
311             mAction = SmilPlayerAction.PAUSE;
312             notifyAll();
313         } else {
314             Log.w(TAG, "Error State: Playback is not playing!");
315         }
316     }
317 
start()318     public synchronized void start() {
319         if (isPausedState()) {
320             resumeActiveElements();
321             mAction = SmilPlayerAction.START;
322             notifyAll();
323         } else if (isPlayedState()) {
324             play();
325         } else {
326             Log.w(TAG, "Error State: Playback can not be started!");
327         }
328     }
329 
stop()330     public synchronized void stop() {
331         if (isPlayingState() || isPausedState()) {
332             mAction = SmilPlayerAction.STOP;
333             notifyAll();
334         } else if (isPlayedState()) {
335             actionStop();
336         }
337     }
338 
stopWhenReload()339     public synchronized void stopWhenReload() {
340         endActiveElements();
341     }
342 
reload()343     public synchronized void reload() {
344         if (isPlayingState() || isPausedState()) {
345             mAction = SmilPlayerAction.RELOAD;
346             notifyAll();
347         } else if (isPlayedState()) {
348             actionReload();
349         }
350     }
351 
isBeginOfSlide(TimelineEntry entry)352     private synchronized boolean isBeginOfSlide(TimelineEntry entry) {
353         return (TimelineEntry.ACTION_BEGIN == entry.getAction())
354                     && (entry.getElement() instanceof SmilParElementImpl);
355     }
356 
reloadActiveSlide()357     private synchronized void reloadActiveSlide() {
358         mActiveElements.clear();
359         beginSmilDocument();
360 
361         for (int i = mCurrentSlide; i < mCurrentElement; i++) {
362             TimelineEntry entry = mAllEntries.get(i);
363             actionEntry(entry);
364         }
365         seekActiveMedia();
366     }
367 
beginSmilDocument()368     private synchronized void beginSmilDocument() {
369         TimelineEntry entry = mAllEntries.get(0);
370         actionEntry(entry);
371     }
372 
getOffsetTime(ElementTime element)373     private synchronized double getOffsetTime(ElementTime element) {
374         for (int i = mCurrentSlide; i < mCurrentElement; i++) {
375             TimelineEntry entry = mAllEntries.get(i);
376             if (element.equals(entry.getElement())) {
377                 return entry.getOffsetTime() * 1000;  // in ms
378             }
379         }
380         return -1;
381     }
382 
seekActiveMedia()383     private synchronized void seekActiveMedia() {
384         for (int i = mActiveElements.size() - 1; i >= 0; i--) {
385             ElementTime element = mActiveElements.get(i);
386             if (element instanceof SmilParElementImpl) {
387                 return;
388             }
389             double offset = getOffsetTime(element);
390             if ((offset >= 0) && (offset <= mCurrentTime)) {
391                 if (LOCAL_LOGV) {
392                     Log.v(TAG, "[SEEK]  " + " at " + mCurrentTime
393                             + " " + element);
394                 }
395                 element.seekElement( (float) (mCurrentTime - offset) );
396             }
397         }
398     }
399 
waitForEntry(long interval)400     private synchronized void waitForEntry(long interval)
401             throws InterruptedException {
402         if (LOCAL_LOGV) {
403             Log.v(TAG, "Waiting for " + interval + "ms.");
404         }
405 
406         long overhead = 0;
407 
408         while (interval > 0) {
409             long startAt = System.currentTimeMillis();
410             long sleep = Math.min(interval, TIMESLICE);
411             if (overhead < sleep) {
412                 wait(sleep - overhead);
413                 mCurrentTime += sleep;
414             } else {
415                 sleep = 0;
416                 mCurrentTime += overhead;
417             }
418 
419             if (isStopAction() || isReloadAction() || isPauseAction()) {
420                 return;
421             }
422 
423             ((EventTarget) mRoot).dispatchEvent(mMediaTimeUpdatedEvent);
424 
425             interval -= TIMESLICE;
426             overhead = System.currentTimeMillis() - startAt - sleep;
427         }
428     }
429 
getDuration()430     public synchronized int getDuration() {
431          if ((mAllEntries != null) && !mAllEntries.isEmpty()) {
432              return (int) mAllEntries.get(mAllEntries.size() - 1).mOffsetTime * 1000;
433          }
434          return 0;
435     }
436 
getCurrentPosition()437     public synchronized int getCurrentPosition() {
438         return (int) mCurrentTime;
439     }
440 
endActiveElements()441     private synchronized void endActiveElements() {
442         for (int i = mActiveElements.size() - 1; i >= 0; i--) {
443             ElementTime element = mActiveElements.get(i);
444             if (LOCAL_LOGV) {
445                 Log.v(TAG, "[STOP]  " + " at " + mCurrentTime
446                         + " " + element);
447             }
448             element.endElement();
449         }
450     }
451 
pauseActiveElements()452     private synchronized void pauseActiveElements() {
453         for (int i = mActiveElements.size() - 1; i >= 0; i--) {
454             ElementTime element = mActiveElements.get(i);
455             if (LOCAL_LOGV) {
456                 Log.v(TAG, "[PAUSE]  " + " at " + mCurrentTime
457                         + " " + element);
458             }
459             element.pauseElement();
460         }
461     }
462 
resumeActiveElements()463     private synchronized void resumeActiveElements() {
464         int size = mActiveElements.size();
465         for (int i = 0; i < size; i++) {
466             ElementTime element = mActiveElements.get(i);
467             if (LOCAL_LOGV) {
468                 Log.v(TAG, "[RESUME]  " + " at " + mCurrentTime
469                         + " " + element);
470             }
471             element.resumeElement();
472         }
473     }
474 
waitForWakeUp()475     private synchronized void waitForWakeUp() {
476         try {
477             while ( !(isStartAction() || isStopAction() || isReloadAction()) ) {
478                 wait(TIMESLICE);
479             }
480             if (isStartAction()) {
481                 mAction = SmilPlayerAction.NO_ACTIVE_ACTION;
482                 mState = SmilPlayerState.PLAYING;
483             }
484         } catch (InterruptedException e) {
485             Log.e(TAG, "Unexpected InterruptedException.", e);
486         }
487     }
488 
actionEntry(TimelineEntry entry)489     private synchronized void actionEntry(TimelineEntry entry) {
490         switch (entry.getAction()) {
491             case TimelineEntry.ACTION_BEGIN:
492                 if (LOCAL_LOGV) {
493                     Log.v(TAG, "[START] " + " at " + mCurrentTime + " "
494                             + entry.getElement());
495                 }
496                 entry.getElement().beginElement();
497                 mActiveElements.add(entry.getElement());
498                 break;
499             case TimelineEntry.ACTION_END:
500                 if (LOCAL_LOGV) {
501                     Log.v(TAG, "[STOP]  " + " at " + mCurrentTime + " "
502                             + entry.getElement());
503                 }
504                 entry.getElement().endElement();
505                 mActiveElements.remove(entry.getElement());
506                 break;
507             default:
508                 break;
509         }
510     }
511 
reloadCurrentEntry()512     private synchronized TimelineEntry reloadCurrentEntry() {
513         return mAllEntries.get(mCurrentElement);
514     }
515 
actionPause()516     private synchronized void actionPause() {
517         pauseActiveElements();
518         mState = SmilPlayerState.PAUSED;
519         mAction = SmilPlayerAction.NO_ACTIVE_ACTION;
520     }
521 
actionStop()522     private synchronized void actionStop() {
523         endActiveElements();
524         mCurrentTime = 0;
525         mCurrentElement = 0;
526         mCurrentSlide = 0;
527         mState = SmilPlayerState.STOPPED;
528         mAction = SmilPlayerAction.NO_ACTIVE_ACTION;
529     }
530 
actionReload()531     private synchronized void actionReload() {
532         reloadActiveSlide();
533         mAction = SmilPlayerAction.NO_ACTIVE_ACTION;
534     }
535 
run()536     public void run() {
537         if (isStoppedState()) {
538             return;
539         }
540 
541         // Play the Element by following the timeline
542         int size = mAllEntries.size();
543         for (mCurrentElement = 0; mCurrentElement < size; mCurrentElement++) {
544             TimelineEntry entry = mAllEntries.get(mCurrentElement);
545             if (isBeginOfSlide(entry)) {
546                 mCurrentSlide = mCurrentElement;
547             }
548             long offset = (long) (entry.getOffsetTime() * 1000); // in ms.
549             while (offset > mCurrentTime) {
550                 try {
551                     waitForEntry(offset - mCurrentTime);
552                 } catch (InterruptedException e) {
553                     Log.e(TAG, "Unexpected InterruptedException.", e);
554                 }
555 
556                 while (isPauseAction() || isStopAction() || isReloadAction()) {
557                     if (isPauseAction()) {
558                         actionPause();
559                         waitForWakeUp();
560                     }
561 
562                     if (isStopAction()) {
563                         actionStop();
564                         return;
565                     }
566 
567                     if (isReloadAction()) {
568                         actionReload();
569                         entry = reloadCurrentEntry();
570                         if (isPausedState()) {
571                             mAction = SmilPlayerAction.PAUSE;
572                         }
573                     }
574                 }
575             }
576             mCurrentTime = offset;
577             actionEntry(entry);
578         }
579 
580         mState = SmilPlayerState.PLAYED;
581     }
582 
583     private static final class TimelineEntry {
584         final static int ACTION_BEGIN = 0;
585         final static int ACTION_END   = 1;
586 
587         private final double mOffsetTime;
588         private final ElementTime mElement;
589         private final int mAction;
590 
TimelineEntry(double offsetTime, ElementTime element, int action)591         public TimelineEntry(double offsetTime, ElementTime element, int action) {
592             mOffsetTime = offsetTime;
593             mElement = element;
594             mAction  = action;
595         }
596 
getOffsetTime()597         public double getOffsetTime() {
598             return mOffsetTime;
599         }
600 
getElement()601         public ElementTime getElement() {
602             return mElement;
603         }
604 
getAction()605         public int getAction() {
606             return mAction;
607         }
608     }
609 }
610