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