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