1 /* 2 * Copyright (C) 2011 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package android.media; 18 19 import android.annotation.UnsupportedAppUsage; 20 import android.graphics.Rect; 21 import android.os.Parcel; 22 import android.util.Log; 23 import java.util.HashMap; 24 import java.util.Set; 25 import java.util.List; 26 import java.util.ArrayList; 27 28 /** 29 * Class to hold the timed text's metadata, including: 30 * <ul> 31 * <li> The characters for rendering</li> 32 * <li> The rendering position for the timed text</li> 33 * </ul> 34 * 35 * <p> To render the timed text, applications need to do the following: 36 * 37 * <ul> 38 * <li> Implement the {@link MediaPlayer.OnTimedTextListener} interface</li> 39 * <li> Register the {@link MediaPlayer.OnTimedTextListener} callback on a MediaPlayer object that is used for playback</li> 40 * <li> When a onTimedText callback is received, do the following: 41 * <ul> 42 * <li> call {@link #getText} to get the characters for rendering</li> 43 * <li> call {@link #getBounds} to get the text rendering area/region</li> 44 * </ul> 45 * </li> 46 * </ul> 47 * 48 * @see android.media.MediaPlayer 49 */ 50 public final class TimedText 51 { 52 private static final int FIRST_PUBLIC_KEY = 1; 53 54 // These keys must be in sync with the keys in TextDescription.h 55 private static final int KEY_DISPLAY_FLAGS = 1; // int 56 private static final int KEY_STYLE_FLAGS = 2; // int 57 private static final int KEY_BACKGROUND_COLOR_RGBA = 3; // int 58 private static final int KEY_HIGHLIGHT_COLOR_RGBA = 4; // int 59 private static final int KEY_SCROLL_DELAY = 5; // int 60 private static final int KEY_WRAP_TEXT = 6; // int 61 private static final int KEY_START_TIME = 7; // int 62 private static final int KEY_STRUCT_BLINKING_TEXT_LIST = 8; // List<CharPos> 63 private static final int KEY_STRUCT_FONT_LIST = 9; // List<Font> 64 private static final int KEY_STRUCT_HIGHLIGHT_LIST = 10; // List<CharPos> 65 private static final int KEY_STRUCT_HYPER_TEXT_LIST = 11; // List<HyperText> 66 private static final int KEY_STRUCT_KARAOKE_LIST = 12; // List<Karaoke> 67 private static final int KEY_STRUCT_STYLE_LIST = 13; // List<Style> 68 private static final int KEY_STRUCT_TEXT_POS = 14; // TextPos 69 private static final int KEY_STRUCT_JUSTIFICATION = 15; // Justification 70 private static final int KEY_STRUCT_TEXT = 16; // Text 71 72 private static final int LAST_PUBLIC_KEY = 16; 73 74 private static final int FIRST_PRIVATE_KEY = 101; 75 76 // The following keys are used between TimedText.java and 77 // TextDescription.cpp in order to parce the Parcel. 78 private static final int KEY_GLOBAL_SETTING = 101; 79 private static final int KEY_LOCAL_SETTING = 102; 80 private static final int KEY_START_CHAR = 103; 81 private static final int KEY_END_CHAR = 104; 82 private static final int KEY_FONT_ID = 105; 83 private static final int KEY_FONT_SIZE = 106; 84 private static final int KEY_TEXT_COLOR_RGBA = 107; 85 86 private static final int LAST_PRIVATE_KEY = 107; 87 88 private static final String TAG = "TimedText"; 89 90 private final HashMap<Integer, Object> mKeyObjectMap = 91 new HashMap<Integer, Object>(); 92 93 private int mDisplayFlags = -1; 94 private int mBackgroundColorRGBA = -1; 95 private int mHighlightColorRGBA = -1; 96 private int mScrollDelay = -1; 97 private int mWrapText = -1; 98 99 private List<CharPos> mBlinkingPosList = null; 100 private List<CharPos> mHighlightPosList = null; 101 private List<Karaoke> mKaraokeList = null; 102 private List<Font> mFontList = null; 103 private List<Style> mStyleList = null; 104 private List<HyperText> mHyperTextList = null; 105 106 private Rect mTextBounds = null; 107 private String mTextChars = null; 108 109 private Justification mJustification; 110 111 /** 112 * Helper class to hold the start char offset and end char offset 113 * for Blinking Text or Highlight Text. endChar is the end offset 114 * of the text (startChar + number of characters to be highlighted 115 * or blinked). The member variables in this class are read-only. 116 * {@hide} 117 */ 118 public static final class CharPos { 119 /** 120 * The offset of the start character 121 */ 122 public final int startChar; 123 124 /** 125 * The offset of the end character 126 */ 127 public final int endChar; 128 129 /** 130 * Constuctor 131 * @param startChar the offset of the start character. 132 * @param endChar the offset of the end character. 133 */ CharPos(int startChar, int endChar)134 public CharPos(int startChar, int endChar) { 135 this.startChar = startChar; 136 this.endChar = endChar; 137 } 138 } 139 140 /** 141 * Helper class to hold the justification for text display in the text box. 142 * The member variables in this class are read-only. 143 * {@hide} 144 */ 145 public static final class Justification { 146 /** 147 * horizontal justification 0: left, 1: centered, -1: right 148 */ 149 public final int horizontalJustification; 150 151 /** 152 * vertical justification 0: top, 1: centered, -1: bottom 153 */ 154 public final int verticalJustification; 155 156 /** 157 * Constructor 158 * @param horizontal the horizontal justification of the text. 159 * @param vertical the vertical justification of the text. 160 */ Justification(int horizontal, int vertical)161 public Justification(int horizontal, int vertical) { 162 this.horizontalJustification = horizontal; 163 this.verticalJustification = vertical; 164 } 165 } 166 167 /** 168 * Helper class to hold the style information to display the text. 169 * The member variables in this class are read-only. 170 * {@hide} 171 */ 172 public static final class Style { 173 /** 174 * The offset of the start character which applys this style 175 */ 176 public final int startChar; 177 178 /** 179 * The offset of the end character which applys this style 180 */ 181 public final int endChar; 182 183 /** 184 * ID of the font. This ID will be used to choose the font 185 * to be used from the font list. 186 */ 187 public final int fontID; 188 189 /** 190 * True if the characters should be bold 191 */ 192 public final boolean isBold; 193 194 /** 195 * True if the characters should be italic 196 */ 197 public final boolean isItalic; 198 199 /** 200 * True if the characters should be underlined 201 */ 202 public final boolean isUnderlined; 203 204 /** 205 * The size of the font 206 */ 207 public final int fontSize; 208 209 /** 210 * To specify the RGBA color: 8 bits each of red, green, blue, 211 * and an alpha(transparency) value 212 */ 213 public final int colorRGBA; 214 215 /** 216 * Constructor 217 * @param startChar the offset of the start character which applys this style 218 * @param endChar the offset of the end character which applys this style 219 * @param fontId the ID of the font. 220 * @param isBold whether the characters should be bold. 221 * @param isItalic whether the characters should be italic. 222 * @param isUnderlined whether the characters should be underlined. 223 * @param fontSize the size of the font. 224 * @param colorRGBA red, green, blue, and alpha value for color. 225 */ Style(int startChar, int endChar, int fontId, boolean isBold, boolean isItalic, boolean isUnderlined, int fontSize, int colorRGBA)226 public Style(int startChar, int endChar, int fontId, 227 boolean isBold, boolean isItalic, boolean isUnderlined, 228 int fontSize, int colorRGBA) { 229 this.startChar = startChar; 230 this.endChar = endChar; 231 this.fontID = fontId; 232 this.isBold = isBold; 233 this.isItalic = isItalic; 234 this.isUnderlined = isUnderlined; 235 this.fontSize = fontSize; 236 this.colorRGBA = colorRGBA; 237 } 238 } 239 240 /** 241 * Helper class to hold the font ID and name. 242 * The member variables in this class are read-only. 243 * {@hide} 244 */ 245 public static final class Font { 246 /** 247 * The font ID 248 */ 249 public final int ID; 250 251 /** 252 * The font name 253 */ 254 public final String name; 255 256 /** 257 * Constructor 258 * @param id the font ID. 259 * @param name the font name. 260 */ Font(int id, String name)261 public Font(int id, String name) { 262 this.ID = id; 263 this.name = name; 264 } 265 } 266 267 /** 268 * Helper class to hold the karaoke information. 269 * The member variables in this class are read-only. 270 * {@hide} 271 */ 272 public static final class Karaoke { 273 /** 274 * The start time (in milliseconds) to highlight the characters 275 * specified by startChar and endChar. 276 */ 277 public final int startTimeMs; 278 279 /** 280 * The end time (in milliseconds) to highlight the characters 281 * specified by startChar and endChar. 282 */ 283 public final int endTimeMs; 284 285 /** 286 * The offset of the start character to be highlighted 287 */ 288 public final int startChar; 289 290 /** 291 * The offset of the end character to be highlighted 292 */ 293 public final int endChar; 294 295 /** 296 * Constructor 297 * @param startTimeMs the start time (in milliseconds) to highlight 298 * the characters between startChar and endChar. 299 * @param endTimeMs the end time (in milliseconds) to highlight 300 * the characters between startChar and endChar. 301 * @param startChar the offset of the start character to be highlighted. 302 * @param endChar the offset of the end character to be highlighted. 303 */ Karaoke(int startTimeMs, int endTimeMs, int startChar, int endChar)304 public Karaoke(int startTimeMs, int endTimeMs, int startChar, int endChar) { 305 this.startTimeMs = startTimeMs; 306 this.endTimeMs = endTimeMs; 307 this.startChar = startChar; 308 this.endChar = endChar; 309 } 310 } 311 312 /** 313 * Helper class to hold the hyper text information. 314 * The member variables in this class are read-only. 315 * {@hide} 316 */ 317 public static final class HyperText { 318 /** 319 * The offset of the start character 320 */ 321 public final int startChar; 322 323 /** 324 * The offset of the end character 325 */ 326 public final int endChar; 327 328 /** 329 * The linked-to URL 330 */ 331 public final String URL; 332 333 /** 334 * The "alt" string for user display 335 */ 336 public final String altString; 337 338 339 /** 340 * Constructor 341 * @param startChar the offset of the start character. 342 * @param endChar the offset of the end character. 343 * @param url the linked-to URL. 344 * @param alt the "alt" string for display. 345 */ HyperText(int startChar, int endChar, String url, String alt)346 public HyperText(int startChar, int endChar, String url, String alt) { 347 this.startChar = startChar; 348 this.endChar = endChar; 349 this.URL = url; 350 this.altString = alt; 351 } 352 } 353 354 /** 355 * @param obj the byte array which contains the timed text. 356 * @throws IllegalArgumentExcept if parseParcel() fails. 357 * {@hide} 358 */ TimedText(Parcel parcel)359 public TimedText(Parcel parcel) { 360 if (!parseParcel(parcel)) { 361 mKeyObjectMap.clear(); 362 throw new IllegalArgumentException("parseParcel() fails"); 363 } 364 } 365 366 /** 367 * @param text the characters in the timed text. 368 * @param bounds the rectangle area or region for rendering the timed text. 369 * {@hide} 370 */ TimedText(String text, Rect bounds)371 public TimedText(String text, Rect bounds) { 372 mTextChars = text; 373 mTextBounds = bounds; 374 } 375 376 /** 377 * Get the characters in the timed text. 378 * 379 * @return the characters as a String object in the TimedText. Applications 380 * should stop rendering previous timed text at the current rendering region if 381 * a null is returned, until the next non-null timed text is received. 382 */ getText()383 public String getText() { 384 return mTextChars; 385 } 386 387 /** 388 * Get the rectangle area or region for rendering the timed text as specified 389 * by a Rect object. 390 * 391 * @return the rectangle region to render the characters in the timed text. 392 * If no bounds information is available (a null is returned), render the 393 * timed text at the center bottom of the display. 394 */ getBounds()395 public Rect getBounds() { 396 return mTextBounds; 397 } 398 399 /* 400 * Go over all the records, collecting metadata keys and fields in the 401 * Parcel. These are stored in mKeyObjectMap for application to retrieve. 402 * @return false if an error occurred during parsing. Otherwise, true. 403 */ parseParcel(Parcel parcel)404 private boolean parseParcel(Parcel parcel) { 405 parcel.setDataPosition(0); 406 if (parcel.dataAvail() == 0) { 407 return false; 408 } 409 410 int type = parcel.readInt(); 411 if (type == KEY_LOCAL_SETTING) { 412 type = parcel.readInt(); 413 if (type != KEY_START_TIME) { 414 return false; 415 } 416 int mStartTimeMs = parcel.readInt(); 417 mKeyObjectMap.put(type, mStartTimeMs); 418 419 type = parcel.readInt(); 420 if (type != KEY_STRUCT_TEXT) { 421 return false; 422 } 423 424 int textLen = parcel.readInt(); 425 byte[] text = parcel.createByteArray(); 426 if (text == null || text.length == 0) { 427 mTextChars = null; 428 } else { 429 mTextChars = new String(text); 430 } 431 432 } else if (type != KEY_GLOBAL_SETTING) { 433 Log.w(TAG, "Invalid timed text key found: " + type); 434 return false; 435 } 436 437 while (parcel.dataAvail() > 0) { 438 int key = parcel.readInt(); 439 if (!isValidKey(key)) { 440 Log.w(TAG, "Invalid timed text key found: " + key); 441 return false; 442 } 443 444 Object object = null; 445 446 switch (key) { 447 case KEY_STRUCT_STYLE_LIST: { 448 readStyle(parcel); 449 object = mStyleList; 450 break; 451 } 452 case KEY_STRUCT_FONT_LIST: { 453 readFont(parcel); 454 object = mFontList; 455 break; 456 } 457 case KEY_STRUCT_HIGHLIGHT_LIST: { 458 readHighlight(parcel); 459 object = mHighlightPosList; 460 break; 461 } 462 case KEY_STRUCT_KARAOKE_LIST: { 463 readKaraoke(parcel); 464 object = mKaraokeList; 465 break; 466 } 467 case KEY_STRUCT_HYPER_TEXT_LIST: { 468 readHyperText(parcel); 469 object = mHyperTextList; 470 471 break; 472 } 473 case KEY_STRUCT_BLINKING_TEXT_LIST: { 474 readBlinkingText(parcel); 475 object = mBlinkingPosList; 476 477 break; 478 } 479 case KEY_WRAP_TEXT: { 480 mWrapText = parcel.readInt(); 481 object = mWrapText; 482 break; 483 } 484 case KEY_HIGHLIGHT_COLOR_RGBA: { 485 mHighlightColorRGBA = parcel.readInt(); 486 object = mHighlightColorRGBA; 487 break; 488 } 489 case KEY_DISPLAY_FLAGS: { 490 mDisplayFlags = parcel.readInt(); 491 object = mDisplayFlags; 492 break; 493 } 494 case KEY_STRUCT_JUSTIFICATION: { 495 496 int horizontal = parcel.readInt(); 497 int vertical = parcel.readInt(); 498 mJustification = new Justification(horizontal, vertical); 499 500 object = mJustification; 501 break; 502 } 503 case KEY_BACKGROUND_COLOR_RGBA: { 504 mBackgroundColorRGBA = parcel.readInt(); 505 object = mBackgroundColorRGBA; 506 break; 507 } 508 case KEY_STRUCT_TEXT_POS: { 509 int top = parcel.readInt(); 510 int left = parcel.readInt(); 511 int bottom = parcel.readInt(); 512 int right = parcel.readInt(); 513 mTextBounds = new Rect(left, top, right, bottom); 514 515 break; 516 } 517 case KEY_SCROLL_DELAY: { 518 mScrollDelay = parcel.readInt(); 519 object = mScrollDelay; 520 break; 521 } 522 default: { 523 break; 524 } 525 } 526 527 if (object != null) { 528 if (mKeyObjectMap.containsKey(key)) { 529 mKeyObjectMap.remove(key); 530 } 531 // Previous mapping will be replaced with the new object, if there was one. 532 mKeyObjectMap.put(key, object); 533 } 534 } 535 536 return true; 537 } 538 539 /* 540 * To parse and store the Style list. 541 */ readStyle(Parcel parcel)542 private void readStyle(Parcel parcel) { 543 boolean endOfStyle = false; 544 int startChar = -1; 545 int endChar = -1; 546 int fontId = -1; 547 boolean isBold = false; 548 boolean isItalic = false; 549 boolean isUnderlined = false; 550 int fontSize = -1; 551 int colorRGBA = -1; 552 while (!endOfStyle && (parcel.dataAvail() > 0)) { 553 int key = parcel.readInt(); 554 switch (key) { 555 case KEY_START_CHAR: { 556 startChar = parcel.readInt(); 557 break; 558 } 559 case KEY_END_CHAR: { 560 endChar = parcel.readInt(); 561 break; 562 } 563 case KEY_FONT_ID: { 564 fontId = parcel.readInt(); 565 break; 566 } 567 case KEY_STYLE_FLAGS: { 568 int flags = parcel.readInt(); 569 // In the absence of any bits set in flags, the text 570 // is plain. Otherwise, 1: bold, 2: italic, 4: underline 571 isBold = ((flags % 2) == 1); 572 isItalic = ((flags % 4) >= 2); 573 isUnderlined = ((flags / 4) == 1); 574 break; 575 } 576 case KEY_FONT_SIZE: { 577 fontSize = parcel.readInt(); 578 break; 579 } 580 case KEY_TEXT_COLOR_RGBA: { 581 colorRGBA = parcel.readInt(); 582 break; 583 } 584 default: { 585 // End of the Style parsing. Reset the data position back 586 // to the position before the last parcel.readInt() call. 587 parcel.setDataPosition(parcel.dataPosition() - 4); 588 endOfStyle = true; 589 break; 590 } 591 } 592 } 593 594 Style style = new Style(startChar, endChar, fontId, isBold, 595 isItalic, isUnderlined, fontSize, colorRGBA); 596 if (mStyleList == null) { 597 mStyleList = new ArrayList<Style>(); 598 } 599 mStyleList.add(style); 600 } 601 602 /* 603 * To parse and store the Font list 604 */ readFont(Parcel parcel)605 private void readFont(Parcel parcel) { 606 int entryCount = parcel.readInt(); 607 608 for (int i = 0; i < entryCount; i++) { 609 int id = parcel.readInt(); 610 int nameLen = parcel.readInt(); 611 612 byte[] text = parcel.createByteArray(); 613 final String name = new String(text, 0, nameLen); 614 615 Font font = new Font(id, name); 616 617 if (mFontList == null) { 618 mFontList = new ArrayList<Font>(); 619 } 620 mFontList.add(font); 621 } 622 } 623 624 /* 625 * To parse and store the Highlight list 626 */ readHighlight(Parcel parcel)627 private void readHighlight(Parcel parcel) { 628 int startChar = parcel.readInt(); 629 int endChar = parcel.readInt(); 630 CharPos pos = new CharPos(startChar, endChar); 631 632 if (mHighlightPosList == null) { 633 mHighlightPosList = new ArrayList<CharPos>(); 634 } 635 mHighlightPosList.add(pos); 636 } 637 638 /* 639 * To parse and store the Karaoke list 640 */ readKaraoke(Parcel parcel)641 private void readKaraoke(Parcel parcel) { 642 int entryCount = parcel.readInt(); 643 644 for (int i = 0; i < entryCount; i++) { 645 int startTimeMs = parcel.readInt(); 646 int endTimeMs = parcel.readInt(); 647 int startChar = parcel.readInt(); 648 int endChar = parcel.readInt(); 649 Karaoke kara = new Karaoke(startTimeMs, endTimeMs, 650 startChar, endChar); 651 652 if (mKaraokeList == null) { 653 mKaraokeList = new ArrayList<Karaoke>(); 654 } 655 mKaraokeList.add(kara); 656 } 657 } 658 659 /* 660 * To parse and store HyperText list 661 */ readHyperText(Parcel parcel)662 private void readHyperText(Parcel parcel) { 663 int startChar = parcel.readInt(); 664 int endChar = parcel.readInt(); 665 666 int len = parcel.readInt(); 667 byte[] url = parcel.createByteArray(); 668 final String urlString = new String(url, 0, len); 669 670 len = parcel.readInt(); 671 byte[] alt = parcel.createByteArray(); 672 final String altString = new String(alt, 0, len); 673 HyperText hyperText = new HyperText(startChar, endChar, urlString, altString); 674 675 676 if (mHyperTextList == null) { 677 mHyperTextList = new ArrayList<HyperText>(); 678 } 679 mHyperTextList.add(hyperText); 680 } 681 682 /* 683 * To parse and store blinking text list 684 */ readBlinkingText(Parcel parcel)685 private void readBlinkingText(Parcel parcel) { 686 int startChar = parcel.readInt(); 687 int endChar = parcel.readInt(); 688 CharPos blinkingPos = new CharPos(startChar, endChar); 689 690 if (mBlinkingPosList == null) { 691 mBlinkingPosList = new ArrayList<CharPos>(); 692 } 693 mBlinkingPosList.add(blinkingPos); 694 } 695 696 /* 697 * To check whether the given key is valid. 698 * @param key the key to be checked. 699 * @return true if the key is a valid one. Otherwise, false. 700 */ isValidKey(final int key)701 private boolean isValidKey(final int key) { 702 if (!((key >= FIRST_PUBLIC_KEY) && (key <= LAST_PUBLIC_KEY)) 703 && !((key >= FIRST_PRIVATE_KEY) && (key <= LAST_PRIVATE_KEY))) { 704 return false; 705 } 706 return true; 707 } 708 709 /* 710 * To check whether the given key is contained in this TimedText object. 711 * @param key the key to be checked. 712 * @return true if the key is contained in this TimedText object. 713 * Otherwise, false. 714 */ containsKey(final int key)715 private boolean containsKey(final int key) { 716 if (isValidKey(key) && mKeyObjectMap.containsKey(key)) { 717 return true; 718 } 719 return false; 720 } 721 722 /* 723 * @return a set of the keys contained in this TimedText object. 724 */ keySet()725 private Set keySet() { 726 return mKeyObjectMap.keySet(); 727 } 728 729 /* 730 * To retrieve the object associated with the key. Caller must make sure 731 * the key is present using the containsKey method otherwise a 732 * RuntimeException will occur. 733 * @param key the key used to retrieve the object. 734 * @return an object. The object could be 1) an instance of Integer; 2) a 735 * List of CharPos, Karaoke, Font, Style, and HyperText, or 3) an instance of 736 * Justification. 737 */ 738 @UnsupportedAppUsage getObject(final int key)739 private Object getObject(final int key) { 740 if (containsKey(key)) { 741 return mKeyObjectMap.get(key); 742 } else { 743 throw new IllegalArgumentException("Invalid key: " + key); 744 } 745 } 746 } 747