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