FileDocCategorySizeDatePackage
SmilPlayer.javaAPI DocAndroid 1.5 API20708Wed May 06 22:42:46 BST 2009com.android.mms.dom.smil

SmilPlayer

public class SmilPlayer extends Object implements Runnable
The SmilPlayer is responsible for playing, stopping, pausing and resuming a SMIL tree.
  • It creates a whole timeline before playing.
  • The player runs in a different thread which intends not to block the main thread.
  • Fields Summary
    private static final String
    TAG
    private static final boolean
    DEBUG
    private static final boolean
    LOCAL_LOGV
    private static final int
    TIMESLICE
    public static final String
    MEDIA_TIME_UPDATED_EVENT
    private static final Comparator
    sTimelineEntryComparator
    private static SmilPlayer
    sPlayer
    private long
    mCurrentTime
    private int
    mCurrentElement
    private int
    mCurrentSlide
    private ArrayList
    mAllEntries
    private org.w3c.dom.smil.ElementTime
    mRoot
    private Thread
    mPlayerThread
    private SmilPlayerState
    mState
    private SmilPlayerAction
    mAction
    private ArrayList
    mActiveElements
    private Event
    mMediaTimeUpdatedEvent
    Constructors Summary
    private SmilPlayer()

            // Private constructor
        
    Methods Summary
    private synchronized voidactionEntry(com.android.mms.dom.smil.SmilPlayer$TimelineEntry entry)

            switch (entry.getAction()) {
                case TimelineEntry.ACTION_BEGIN:
                    if (LOCAL_LOGV) {
                        Log.v(TAG, "[START] " + " at " + mCurrentTime + " "
                                + entry.getElement());
                    }
                    entry.getElement().beginElement();
                    mActiveElements.add(entry.getElement());
                    break;
                case TimelineEntry.ACTION_END:
                    if (LOCAL_LOGV) {
                        Log.v(TAG, "[STOP]  " + " at " + mCurrentTime + " "
                                + entry.getElement());
                    }
                    entry.getElement().endElement();
                    mActiveElements.remove(entry.getElement());
                    break;
                default:
                    break;
            }
        
    private synchronized voidactionPause()

            pauseActiveElements();
            mState = SmilPlayerState.PAUSED;
            mAction = SmilPlayerAction.NO_ACTIVE_ACTION;
        
    private synchronized voidactionReload()

            reloadActiveSlide();
            mAction = SmilPlayerAction.NO_ACTIVE_ACTION;
        
    private synchronized voidactionStop()

            endActiveElements();
            mCurrentTime = 0;
            mCurrentElement = 0;
            mCurrentSlide = 0;
            mState = SmilPlayerState.STOPPED;
            mAction = SmilPlayerAction.NO_ACTIVE_ACTION;
        
    private synchronized voidbeginSmilDocument()

            TimelineEntry entry = mAllEntries.get(0);
            actionEntry(entry);
        
    private synchronized voidendActiveElements()

            for (int i = mActiveElements.size() - 1; i >= 0; i--) {
                ElementTime element = mActiveElements.get(i);
                if (LOCAL_LOGV) {
                    Log.v(TAG, "[STOP]  " + " at " + mCurrentTime
                            + " " + element);
                }
                element.endElement();
            }
        
    public synchronized intgetCurrentPosition()

            return (int) mCurrentTime;
        
    public synchronized intgetDuration()

             if ((mAllEntries != null) && !mAllEntries.isEmpty()) {
                 return (int) mAllEntries.get(mAllEntries.size() - 1).mOffsetTime * 1000;
             }
             return 0;
        
    private synchronized doublegetOffsetTime(org.w3c.dom.smil.ElementTime element)

            for (int i = mCurrentSlide; i < mCurrentElement; i++) {
                TimelineEntry entry = mAllEntries.get(i);
                if (element.equals(entry.getElement())) {
                    return entry.getOffsetTime() * 1000;  // in ms
                }
            }
            return -1;
        
    private static java.util.ArrayListgetParTimeline(org.w3c.dom.smil.ElementParallelTimeContainer par, double offset, double maxOffset)

    
           
                      
            ArrayList<TimelineEntry> timeline = new ArrayList<TimelineEntry>();
    
            // Set my begin at first
            TimeList myBeginList = par.getBegin();
            /*
             * Begin list only contain 1 begin time which has been resolved.
             * @see com.android.mms.dom.smil.ElementParallelTimeContainerImpl#getBegin()
             */
            Time begin = myBeginList.item(0);
            double beginOffset = begin.getResolvedOffset() + offset;
            if (beginOffset > maxOffset) {
                // This element can't be started.
                return timeline;
            }
            TimelineEntry myBegin = new TimelineEntry(beginOffset, par, TimelineEntry.ACTION_BEGIN);
            timeline.add(myBegin);
    
            TimeList myEndList = par.getEnd();
            /*
             * End list only contain 1 end time which has been resolved.
             * @see com.android.mms.dom.smil.ElementParallelTimeContainerImpl#getEnd()
             */
            Time end = myEndList.item(0);
            double endOffset = end.getResolvedOffset() + offset;
            if (endOffset > maxOffset) {
                endOffset = maxOffset;
            }
            TimelineEntry myEnd = new TimelineEntry(endOffset, par, TimelineEntry.ACTION_END);
    
            maxOffset = endOffset;
    
            NodeList children = par.getTimeChildren();
            for (int i = 0; i < children.getLength(); ++i) {
                ElementTime child = (ElementTime) children.item(i);
                ArrayList<TimelineEntry> childTimeline = getTimeline(child, offset, maxOffset);
                timeline.addAll(childTimeline);
            }
    
            Collections.sort(timeline, sTimelineEntryComparator);
    
            // Add end-event to timeline for all active children
            NodeList activeChildrenAtEnd = par.getActiveChildrenAt(
                    (float) (endOffset - offset) * 1000);
            for (int i = 0; i < activeChildrenAtEnd.getLength(); ++i) {
                timeline.add(new TimelineEntry(endOffset,
                        (ElementTime) activeChildrenAtEnd.item(i),
                        TimelineEntry.ACTION_END));
            }
    
            // Set my end at last
            timeline.add(myEnd);
    
            return timeline;
        
    public static com.android.mms.dom.smil.SmilPlayergetPlayer()

            if (sPlayer == null) {
                sPlayer = new SmilPlayer();
            }
            return sPlayer;
        
    private static java.util.ArrayListgetSeqTimeline(org.w3c.dom.smil.ElementSequentialTimeContainer seq, double offset, double maxOffset)

            ArrayList<TimelineEntry> timeline = new ArrayList<TimelineEntry>();
            double orgOffset = offset;
    
            // Set my begin at first
            TimeList myBeginList = seq.getBegin();
            /*
             * Begin list only contain 1 begin time which has been resolved.
             * @see com.android.mms.dom.smil.ElementSequentialTimeContainerImpl#getBegin()
             */
            Time begin = myBeginList.item(0);
            double beginOffset = begin.getResolvedOffset() + offset;
            if (beginOffset > maxOffset) {
                // This element can't be started.
                return timeline;
            }
            TimelineEntry myBegin = new TimelineEntry(beginOffset, seq, TimelineEntry.ACTION_BEGIN);
            timeline.add(myBegin);
    
            TimeList myEndList = seq.getEnd();
            /*
             * End list only contain 1 end time which has been resolved.
             * @see com.android.mms.dom.smil.ElementSequentialTimeContainerImpl#getEnd()
             */
            Time end = myEndList.item(0);
            double endOffset = end.getResolvedOffset() + offset;
            if (endOffset > maxOffset) {
                endOffset = maxOffset;
            }
            TimelineEntry myEnd = new TimelineEntry(endOffset, seq, TimelineEntry.ACTION_END);
    
            maxOffset = endOffset;
    
            // Get children's timelines
            NodeList children = seq.getTimeChildren();
            for (int i = 0; i < children.getLength(); ++i) {
                ElementTime child = (ElementTime) children.item(i);
                ArrayList<TimelineEntry> childTimeline = getTimeline(child, offset, maxOffset);
                timeline.addAll(childTimeline);
    
                // Since the child timeline has been sorted, the offset of the last one is the biggest.
                offset = childTimeline.get(childTimeline.size() - 1).getOffsetTime();
            }
    
            // Add end-event to timeline for all active children
            NodeList activeChildrenAtEnd = seq.getActiveChildrenAt(
                    (float) (endOffset - orgOffset));
            for (int i = 0; i < activeChildrenAtEnd.getLength(); ++i) {
                timeline.add(new TimelineEntry(endOffset,
                        (ElementTime) activeChildrenAtEnd.item(i),
                        TimelineEntry.ACTION_END));
            }
    
            // Set my end at last
            timeline.add(myEnd);
    
            return timeline;
        
    private static java.util.ArrayListgetTimeline(org.w3c.dom.smil.ElementTime element, double offset, double maxOffset)

            if (element instanceof ElementParallelTimeContainer) {
                return getParTimeline((ElementParallelTimeContainer) element, offset, maxOffset);
            } else if (element instanceof ElementSequentialTimeContainer) {
                return getSeqTimeline((ElementSequentialTimeContainer) element, offset, maxOffset);
            } else {
                // Not ElementTimeContainer here
                ArrayList<TimelineEntry> timeline = new ArrayList<TimelineEntry>();
    
                TimeList beginList = element.getBegin();
                for (int i = 0; i < beginList.getLength(); ++i) {
                    Time begin = beginList.item(i);
                    if (begin.getResolved()) {
                        double beginOffset = begin.getResolvedOffset() + offset;
                        if (beginOffset <= maxOffset) {
                            TimelineEntry entry = new TimelineEntry(beginOffset,
                                    element, TimelineEntry.ACTION_BEGIN);
                            timeline.add(entry);
                        }
                    }
                }
    
                TimeList endList = element.getEnd();
                for (int i = 0; i < endList.getLength(); ++i) {
                    Time end = endList.item(i);
                    if (end.getResolved()) {
                        double endOffset = end.getResolvedOffset() + offset;
                        if (endOffset <= maxOffset) {
                            TimelineEntry entry = new TimelineEntry(endOffset,
                                    element, TimelineEntry.ACTION_END);
                            timeline.add(entry);
                        }
                    }
                }
    
                Collections.sort(timeline, sTimelineEntryComparator);
    
                return timeline;
            }
        
    public synchronized voidinit(org.w3c.dom.smil.ElementTime root)

            mRoot = root;
            mAllEntries = getTimeline(mRoot, 0, Long.MAX_VALUE);
            mMediaTimeUpdatedEvent = ((DocumentEvent) mRoot).createEvent("Event");
            mMediaTimeUpdatedEvent.initEvent(MEDIA_TIME_UPDATED_EVENT, false, false);
            mActiveElements = new ArrayList<ElementTime>();
        
    private synchronized booleanisBeginOfSlide(com.android.mms.dom.smil.SmilPlayer$TimelineEntry entry)

            return (TimelineEntry.ACTION_BEGIN == entry.getAction())
                        && (entry.getElement() instanceof SmilParElementImpl);
        
    private synchronized booleanisPauseAction()

            return mAction == SmilPlayerAction.PAUSE;
        
    public synchronized booleanisPausedState()

            return mState == SmilPlayerState.PAUSED;
        
    public synchronized booleanisPlayedState()

            return mState == SmilPlayerState.PLAYED;
        
    public synchronized booleanisPlayingState()

            return mState == SmilPlayerState.PLAYING;
        
    private synchronized booleanisReloadAction()

            return mAction == SmilPlayerAction.RELOAD;
        
    private synchronized booleanisStartAction()

            return mAction == SmilPlayerAction.START;
        
    private synchronized booleanisStopAction()

            return mAction == SmilPlayerAction.STOP;
        
    public synchronized booleanisStoppedState()

            return mState == SmilPlayerState.STOPPED;
        
    public synchronized voidpause()

            if (isPlayingState()) {
                mAction = SmilPlayerAction.PAUSE;
                notifyAll();
            } else {
                Log.w(TAG, "Error State: Playback is not playing!");
            }
        
    private synchronized voidpauseActiveElements()

            for (int i = mActiveElements.size() - 1; i >= 0; i--) {
                ElementTime element = mActiveElements.get(i);
                if (LOCAL_LOGV) {
                    Log.v(TAG, "[PAUSE]  " + " at " + mCurrentTime
                            + " " + element);
                }
                element.pauseElement();
            }
        
    public synchronized voidplay()

            if (!isPlayingState()) {
                mCurrentTime = 0;
                mCurrentElement = 0;
                mCurrentSlide = 0;
                mPlayerThread = new Thread(this);
                mState = SmilPlayerState.PLAYING;
                mPlayerThread.start();
            } else {
                Log.w(TAG, "Error State: Playback is playing!");
            }
        
    public synchronized voidreload()

            if (isPlayingState() || isPausedState()) {
                mAction = SmilPlayerAction.RELOAD;
                notifyAll();
            } else if (isPlayedState()) {
                actionReload();
            }
        
    private synchronized voidreloadActiveSlide()

            mActiveElements.clear();
            beginSmilDocument();
    
            for (int i = mCurrentSlide; i < mCurrentElement; i++) {
                TimelineEntry entry = mAllEntries.get(i);
                actionEntry(entry);
            }
            seekActiveMedia();
        
    private synchronized com.android.mms.dom.smil.SmilPlayer$TimelineEntryreloadCurrentEntry()

            return mAllEntries.get(mCurrentElement);
        
    private synchronized voidresumeActiveElements()

            int size = mActiveElements.size();
            for (int i = 0; i < size; i++) {
                ElementTime element = mActiveElements.get(i);
                if (LOCAL_LOGV) {
                    Log.v(TAG, "[RESUME]  " + " at " + mCurrentTime
                            + " " + element);
                }
                element.resumeElement();
            }
        
    public voidrun()

            if (isStoppedState()) {
                return;
            }
    
            // Play the Element by following the timeline
            int size = mAllEntries.size();
            for (mCurrentElement = 0; mCurrentElement < size; mCurrentElement++) {
                TimelineEntry entry = mAllEntries.get(mCurrentElement);
                if (isBeginOfSlide(entry)) {
                    mCurrentSlide = mCurrentElement;
                }
                long offset = (long) (entry.getOffsetTime() * 1000); // in ms.
                while (offset > mCurrentTime) {
                    try {
                        waitForEntry(offset - mCurrentTime);
                    } catch (InterruptedException e) {
                        Log.e(TAG, "Unexpected InterruptedException.", e);
                    }
    
                    while (isPauseAction() || isStopAction() || isReloadAction()) {
                        if (isPauseAction()) {
                            actionPause();
                            waitForWakeUp();
                        }
    
                        if (isStopAction()) {
                            actionStop();
                            return;
                        }
    
                        if (isReloadAction()) {
                            actionReload();
                            entry = reloadCurrentEntry();
                            if (isPausedState()) {
                                mAction = SmilPlayerAction.PAUSE;
                            }
                        }
                    }
                }
                mCurrentTime = offset;
                actionEntry(entry);
            }
    
            mState = SmilPlayerState.PLAYED;
        
    private synchronized voidseekActiveMedia()

            for (int i = mActiveElements.size() - 1; i >= 0; i--) {
                ElementTime element = mActiveElements.get(i);
                if (element instanceof SmilParElementImpl) {
                    return;
                }
                double offset = getOffsetTime(element);
                if ((offset >= 0) && (offset <= mCurrentTime)) {
                    if (LOCAL_LOGV) {
                        Log.v(TAG, "[SEEK]  " + " at " + mCurrentTime
                                + " " + element);
                    }
                    element.seekElement( (float) (mCurrentTime - offset) );
                }
            }
        
    public synchronized voidstart()

            if (isPausedState()) {
                resumeActiveElements();
                mAction = SmilPlayerAction.START;
                notifyAll();
            } else if (isPlayedState()) {
                play();
            } else {
                Log.w(TAG, "Error State: Playback can not be started!");
            }
        
    public synchronized voidstop()

            if (isPlayingState() || isPausedState()) {
                mAction = SmilPlayerAction.STOP;
                notifyAll();
            } else if (isPlayedState()) {
                actionStop();
            }
        
    public synchronized voidstopWhenReload()

            endActiveElements();
        
    private synchronized voidwaitForEntry(long interval)

            if (LOCAL_LOGV) {
                Log.v(TAG, "Waiting for " + interval + "ms.");
            }
    
            long overhead = 0;
    
            while (interval > 0) {
                long startAt = System.currentTimeMillis();
                long sleep = Math.min(interval, TIMESLICE);
                if (overhead < sleep) {
                    wait(sleep - overhead);
                    mCurrentTime += sleep;
                } else {
                    sleep = 0;
                    mCurrentTime += overhead;
                }
    
                if (isStopAction() || isReloadAction() || isPauseAction()) {
                    return;
                }
    
                ((EventTarget) mRoot).dispatchEvent(mMediaTimeUpdatedEvent);
    
                interval -= TIMESLICE;
                overhead = System.currentTimeMillis() - startAt - sleep;
            }
        
    private synchronized voidwaitForWakeUp()

            try {
                while ( !(isStartAction() || isStopAction() || isReloadAction()) ) {
                    wait(TIMESLICE);
                }
                if (isStartAction()) {
                    mAction = SmilPlayerAction.NO_ACTIVE_ACTION;
                    mState = SmilPlayerState.PLAYING;
                }
            } catch (InterruptedException e) {
                Log.e(TAG, "Unexpected InterruptedException.", e);
            }