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 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 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 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 248 private SmilPlayer() { 249 // Private constructor 250 } 251 252 public static SmilPlayer getPlayer() { 253 if (sPlayer == null) { 254 sPlayer = new SmilPlayer(); 255 } 256 return sPlayer; 257 } 258 259 public synchronized boolean isPlayingState() { 260 return mState == SmilPlayerState.PLAYING; 261 } 262 263 public synchronized boolean isPlayedState() { 264 return mState == SmilPlayerState.PLAYED; 265 } 266 267 public synchronized boolean isPausedState() { 268 return mState == SmilPlayerState.PAUSED; 269 } 270 271 public synchronized boolean isStoppedState() { 272 return mState == SmilPlayerState.STOPPED; 273 } 274 275 private synchronized boolean isPauseAction() { 276 return mAction == SmilPlayerAction.PAUSE; 277 } 278 279 private synchronized boolean isStartAction() { 280 return mAction == SmilPlayerAction.START; 281 } 282 283 private synchronized boolean isStopAction() { 284 return mAction == SmilPlayerAction.STOP; 285 } 286 287 private synchronized boolean isReloadAction() { 288 return mAction == SmilPlayerAction.RELOAD; 289 } 290 291 private synchronized boolean isNextAction() { 292 return mAction == SmilPlayerAction.NEXT; 293 } 294 295 private synchronized boolean isPrevAction() { 296 return mAction == SmilPlayerAction.PREV; 297 } 298 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 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 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 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 341 public synchronized void stop() { 342 if (isPlayingState() || isPausedState()) { 343 mAction = SmilPlayerAction.STOP; 344 notifyAll(); 345 } else if (isPlayedState()) { 346 actionStop(); 347 } 348 } 349 350 public synchronized void stopWhenReload() { 351 endActiveElements(); 352 } 353 354 public synchronized void reload() { 355 if (isPlayingState() || isPausedState()) { 356 mAction = SmilPlayerAction.RELOAD; 357 notifyAll(); 358 } else if (isPlayedState()) { 359 actionReload(); 360 } 361 } 362 363 public synchronized void next() { 364 if (isPlayingState() || isPausedState()) { 365 mAction = SmilPlayerAction.NEXT; 366 notifyAll(); 367 } 368 } 369 370 public synchronized void prev() { 371 if (isPlayingState() || isPausedState()) { 372 mAction = SmilPlayerAction.PREV; 373 notifyAll(); 374 } 375 } 376 377 private synchronized boolean isBeginOfSlide(TimelineEntry entry) { 378 return (TimelineEntry.ACTION_BEGIN == entry.getAction()) 379 && (entry.getElement() instanceof SmilParElementImpl); 380 } 381 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 393 private synchronized void beginSmilDocument() { 394 TimelineEntry entry = mAllEntries.get(0); 395 actionEntry(entry); 396 } 397 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 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 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 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 463 public synchronized int getCurrentPosition() { 464 return (int) mCurrentTime; 465 } 466 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 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 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 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 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 539 private synchronized TimelineEntry reloadCurrentEntry() { 540 return mAllEntries.get(mCurrentElement); 541 } 542 543 private void stopCurrentSlide() { 544 HashSet<TimelineEntry> skippedEntries = new HashSet<TimelineEntry>(); 545 int totalEntries = mAllEntries.size(); 546 for (int i = mCurrentElement; i < totalEntries; i++) { 547 // Stop any started entries, and skip the not started entries until 548 // meeting the end of slide 549 TimelineEntry entry = mAllEntries.get(i); 550 int action = entry.getAction(); 551 if (entry.getElement() instanceof SmilParElementImpl && 552 action == TimelineEntry.ACTION_END) { 553 actionEntry(entry); 554 mCurrentElement = i; 555 break; 556 } else if (action == TimelineEntry.ACTION_END && !skippedEntries.contains(entry)) { 557 actionEntry(entry); 558 } else if (action == TimelineEntry.ACTION_BEGIN) { 559 skippedEntries.add(entry); 560 } 561 } 562 } 563 564 private TimelineEntry loadNextSlide() { 565 TimelineEntry entry; 566 int totalEntries = mAllEntries.size(); 567 for (int i = mCurrentElement; i < totalEntries; i++) { 568 entry = mAllEntries.get(i); 569 if (isBeginOfSlide(entry)) { 570 mCurrentElement = i; 571 mCurrentSlide = i; 572 mCurrentTime = (long)(entry.getOffsetTime() * 1000); 573 return entry; 574 } 575 } 576 // No slide, finish play back 577 mCurrentElement++; 578 entry = null; 579 if (mCurrentElement < totalEntries) { 580 entry = mAllEntries.get(mCurrentElement); 581 mCurrentTime = (long)(entry.getOffsetTime() * 1000); 582 } 583 return entry; 584 } 585 586 private TimelineEntry loadPrevSlide() { 587 int skippedSlides = 1; 588 int latestBeginEntryIndex = -1; 589 for (int i = mCurrentSlide; i >= 0; i--) { 590 TimelineEntry entry = mAllEntries.get(i); 591 if (isBeginOfSlide(entry)) { 592 latestBeginEntryIndex = i; 593 if (0 == skippedSlides-- ) { 594 mCurrentElement = i; 595 mCurrentSlide = i; 596 mCurrentTime = (long)(entry.getOffsetTime() * 1000); 597 return entry; 598 } 599 } 600 } 601 if (latestBeginEntryIndex != -1) { 602 mCurrentElement = latestBeginEntryIndex; 603 mCurrentSlide = latestBeginEntryIndex; 604 return mAllEntries.get(mCurrentElement); 605 } 606 return null; 607 } 608 609 private synchronized TimelineEntry actionNext() { 610 stopCurrentSlide(); 611 return loadNextSlide(); 612 } 613 614 private synchronized TimelineEntry actionPrev() { 615 stopCurrentSlide(); 616 return loadPrevSlide(); 617 } 618 619 private synchronized void actionPause() { 620 pauseActiveElements(); 621 mState = SmilPlayerState.PAUSED; 622 mAction = SmilPlayerAction.NO_ACTIVE_ACTION; 623 } 624 625 private synchronized void actionStop() { 626 endActiveElements(); 627 mCurrentTime = 0; 628 mCurrentElement = 0; 629 mCurrentSlide = 0; 630 mState = SmilPlayerState.STOPPED; 631 mAction = SmilPlayerAction.NO_ACTIVE_ACTION; 632 } 633 634 private synchronized void actionReload() { 635 reloadActiveSlide(); 636 mAction = SmilPlayerAction.NO_ACTIVE_ACTION; 637 } 638 639 public void run() { 640 if (isStoppedState()) { 641 return; 642 } 643 if (LOCAL_LOGV) { 644 dumpAllEntries(); 645 } 646 // Play the Element by following the timeline 647 int size = mAllEntries.size(); 648 for (mCurrentElement = 0; mCurrentElement < size; mCurrentElement++) { 649 TimelineEntry entry = mAllEntries.get(mCurrentElement); 650 if (isBeginOfSlide(entry)) { 651 mCurrentSlide = mCurrentElement; 652 } 653 long offset = (long) (entry.getOffsetTime() * 1000); // in ms. 654 while (offset > mCurrentTime) { 655 try { 656 waitForEntry(offset - mCurrentTime); 657 } catch (InterruptedException e) { 658 Log.e(TAG, "Unexpected InterruptedException.", e); 659 } 660 661 while (isPauseAction() || isStopAction() || isReloadAction() || isNextAction() || 662 isPrevAction()) { 663 if (isPauseAction()) { 664 actionPause(); 665 waitForWakeUp(); 666 } 667 668 if (isStopAction()) { 669 actionStop(); 670 return; 671 } 672 673 if (isReloadAction()) { 674 actionReload(); 675 entry = reloadCurrentEntry(); 676 if (isPausedState()) { 677 mAction = SmilPlayerAction.PAUSE; 678 } 679 } 680 681 if (isNextAction()) { 682 TimelineEntry nextEntry = actionNext(); 683 if (nextEntry != null) { 684 entry = nextEntry; 685 } 686 if (mState == SmilPlayerState.PAUSED) { 687 mAction = SmilPlayerAction.PAUSE; 688 actionEntry(entry); 689 } else { 690 mAction = SmilPlayerAction.NO_ACTIVE_ACTION; 691 } 692 offset = mCurrentTime; 693 } 694 695 if (isPrevAction()) { 696 TimelineEntry prevEntry = actionPrev(); 697 if (prevEntry != null) { 698 entry = prevEntry; 699 } 700 if (mState == SmilPlayerState.PAUSED) { 701 mAction = SmilPlayerAction.PAUSE; 702 actionEntry(entry); 703 } else { 704 mAction = SmilPlayerAction.NO_ACTIVE_ACTION; 705 } 706 offset = mCurrentTime; 707 } 708 } 709 } 710 mCurrentTime = offset; 711 actionEntry(entry); 712 } 713 714 mState = SmilPlayerState.PLAYED; 715 } 716 717 private static final class TimelineEntry { 718 final static int ACTION_BEGIN = 0; 719 final static int ACTION_END = 1; 720 721 private final double mOffsetTime; 722 private final ElementTime mElement; 723 private final int mAction; 724 725 public TimelineEntry(double offsetTime, ElementTime element, int action) { 726 mOffsetTime = offsetTime; 727 mElement = element; 728 mAction = action; 729 } 730 731 public double getOffsetTime() { 732 return mOffsetTime; 733 } 734 735 public ElementTime getElement() { 736 return mElement; 737 } 738 739 public int getAction() { 740 return mAction; 741 } 742 743 public String toString() { 744 return "Type = " + mElement + " offset = " + getOffsetTime() + " action = " + getAction(); 745 } 746 } 747 748 private void dumpAllEntries() { 749 if (LOCAL_LOGV) { 750 for (TimelineEntry entry : mAllEntries) { 751 Log.v(TAG, "[Entry] "+ entry); 752 } 753 } 754 } 755 } 756