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 */ 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 */ 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 */ 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 */ 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 */ 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 */ 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 */ 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 */ 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 */ 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 */ 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 */ 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 */ 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 */ 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 */ 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 */ 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 */ 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 */ 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 */ 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 */ 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 */ 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