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