TimedText.java revision 7a9734d769d97470ce6fac0594dd007804d33432
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 17package android.media; 18 19import android.graphics.Rect; 20import android.os.Parcel; 21import android.util.Log; 22import java.util.HashMap; 23import java.util.Set; 24import java.util.List; 25import 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 postion 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 */ 49public 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 Parcel mParcel = Parcel.obtain(); 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 */ 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 */ 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 */ 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 */ 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 */ 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 */ 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 */ 359 public TimedText(byte[] obj) { 360 mParcel.unmarshall(obj, 0, obj.length); 361 362 if (!parseParcel()) { 363 mKeyObjectMap.clear(); 364 throw new IllegalArgumentException("parseParcel() fails"); 365 } 366 } 367 368 /** 369 * Get the characters in the timed text. 370 * 371 * @return the characters as a String object in the TimedText. Applications 372 * should stop rendering previous timed text at the current rendering region if 373 * a null is returned, until the next non-null timed text is received. 374 */ 375 public String getText() { 376 return mTextChars; 377 } 378 379 /** 380 * Get the rectangle area or region for rendering the timed text as specified 381 * by a Rect object. 382 * 383 * @return the rectangle region to render the characters in the timed text. 384 * If no bounds information is available (a null is returned), render the 385 * timed text at the center bottom of the display. 386 */ 387 public Rect getBounds() { 388 return mTextBounds; 389 } 390 391 /* 392 * Go over all the records, collecting metadata keys and fields in the 393 * Parcel. These are stored in mKeyObjectMap for application to retrieve. 394 * @return false if an error occurred during parsing. Otherwise, true. 395 */ 396 private boolean parseParcel() { 397 mParcel.setDataPosition(0); 398 if (mParcel.dataAvail() == 0) { 399 return false; 400 } 401 402 int type = mParcel.readInt(); 403 if (type == KEY_LOCAL_SETTING) { 404 type = mParcel.readInt(); 405 if (type != KEY_START_TIME) { 406 return false; 407 } 408 int mStartTimeMs = mParcel.readInt(); 409 mKeyObjectMap.put(type, mStartTimeMs); 410 411 type = mParcel.readInt(); 412 if (type != KEY_STRUCT_TEXT) { 413 return false; 414 } 415 416 int textLen = mParcel.readInt(); 417 byte[] text = mParcel.createByteArray(); 418 if (text == null || text.length == 0) { 419 mTextChars = null; 420 } else { 421 mTextChars = new String(text); 422 } 423 424 } else if (type != KEY_GLOBAL_SETTING) { 425 Log.w(TAG, "Invalid timed text key found: " + type); 426 return false; 427 } 428 429 while (mParcel.dataAvail() > 0) { 430 int key = mParcel.readInt(); 431 if (!isValidKey(key)) { 432 Log.w(TAG, "Invalid timed text key found: " + key); 433 return false; 434 } 435 436 Object object = null; 437 438 switch (key) { 439 case KEY_STRUCT_STYLE_LIST: { 440 readStyle(); 441 object = mStyleList; 442 break; 443 } 444 case KEY_STRUCT_FONT_LIST: { 445 readFont(); 446 object = mFontList; 447 break; 448 } 449 case KEY_STRUCT_HIGHLIGHT_LIST: { 450 readHighlight(); 451 object = mHighlightPosList; 452 break; 453 } 454 case KEY_STRUCT_KARAOKE_LIST: { 455 readKaraoke(); 456 object = mKaraokeList; 457 break; 458 } 459 case KEY_STRUCT_HYPER_TEXT_LIST: { 460 readHyperText(); 461 object = mHyperTextList; 462 463 break; 464 } 465 case KEY_STRUCT_BLINKING_TEXT_LIST: { 466 readBlinkingText(); 467 object = mBlinkingPosList; 468 469 break; 470 } 471 case KEY_WRAP_TEXT: { 472 mWrapText = mParcel.readInt(); 473 object = mWrapText; 474 break; 475 } 476 case KEY_HIGHLIGHT_COLOR_RGBA: { 477 mHighlightColorRGBA = mParcel.readInt(); 478 object = mHighlightColorRGBA; 479 break; 480 } 481 case KEY_DISPLAY_FLAGS: { 482 mDisplayFlags = mParcel.readInt(); 483 object = mDisplayFlags; 484 break; 485 } 486 case KEY_STRUCT_JUSTIFICATION: { 487 488 int horizontal = mParcel.readInt(); 489 int vertical = mParcel.readInt(); 490 mJustification = new Justification(horizontal, vertical); 491 492 object = mJustification; 493 break; 494 } 495 case KEY_BACKGROUND_COLOR_RGBA: { 496 mBackgroundColorRGBA = mParcel.readInt(); 497 object = mBackgroundColorRGBA; 498 break; 499 } 500 case KEY_STRUCT_TEXT_POS: { 501 int top = mParcel.readInt(); 502 int left = mParcel.readInt(); 503 int bottom = mParcel.readInt(); 504 int right = mParcel.readInt(); 505 mTextBounds = new Rect(left, top, right, bottom); 506 507 break; 508 } 509 case KEY_SCROLL_DELAY: { 510 mScrollDelay = mParcel.readInt(); 511 object = mScrollDelay; 512 break; 513 } 514 default: { 515 break; 516 } 517 } 518 519 if (object != null) { 520 if (mKeyObjectMap.containsKey(key)) { 521 mKeyObjectMap.remove(key); 522 } 523 mKeyObjectMap.put(key, object); 524 } 525 } 526 527 mParcel.recycle(); 528 return true; 529 } 530 531 /* 532 * To parse and store the Style list. 533 */ 534 private void readStyle() { 535 boolean endOfStyle = false; 536 int startChar = -1; 537 int endChar = -1; 538 int fontId = -1; 539 boolean isBold = false; 540 boolean isItalic = false; 541 boolean isUnderlined = false; 542 int fontSize = -1; 543 int colorRGBA = -1; 544 while (!endOfStyle && (mParcel.dataAvail() > 0)) { 545 int key = mParcel.readInt(); 546 switch (key) { 547 case KEY_START_CHAR: { 548 startChar = mParcel.readInt(); 549 break; 550 } 551 case KEY_END_CHAR: { 552 endChar = mParcel.readInt(); 553 break; 554 } 555 case KEY_FONT_ID: { 556 fontId = mParcel.readInt(); 557 break; 558 } 559 case KEY_STYLE_FLAGS: { 560 int flags = mParcel.readInt(); 561 // In the absence of any bits set in flags, the text 562 // is plain. Otherwise, 1: bold, 2: italic, 4: underline 563 isBold = ((flags % 2) == 1); 564 isItalic = ((flags % 4) >= 2); 565 isUnderlined = ((flags / 4) == 1); 566 break; 567 } 568 case KEY_FONT_SIZE: { 569 fontSize = mParcel.readInt(); 570 break; 571 } 572 case KEY_TEXT_COLOR_RGBA: { 573 colorRGBA = mParcel.readInt(); 574 break; 575 } 576 default: { 577 // End of the Style parsing. Reset the data position back 578 // to the position before the last mParcel.readInt() call. 579 mParcel.setDataPosition(mParcel.dataPosition() - 4); 580 endOfStyle = true; 581 break; 582 } 583 } 584 } 585 586 Style style = new Style(startChar, endChar, fontId, isBold, 587 isItalic, isUnderlined, fontSize, colorRGBA); 588 if (mStyleList == null) { 589 mStyleList = new ArrayList<Style>(); 590 } 591 mStyleList.add(style); 592 } 593 594 /* 595 * To parse and store the Font list 596 */ 597 private void readFont() { 598 int entryCount = mParcel.readInt(); 599 600 for (int i = 0; i < entryCount; i++) { 601 int id = mParcel.readInt(); 602 int nameLen = mParcel.readInt(); 603 604 byte[] text = mParcel.createByteArray(); 605 final String name = new String(text, 0, nameLen); 606 607 Font font = new Font(id, name); 608 609 if (mFontList == null) { 610 mFontList = new ArrayList<Font>(); 611 } 612 mFontList.add(font); 613 } 614 } 615 616 /* 617 * To parse and store the Highlight list 618 */ 619 private void readHighlight() { 620 int startChar = mParcel.readInt(); 621 int endChar = mParcel.readInt(); 622 CharPos pos = new CharPos(startChar, endChar); 623 624 if (mHighlightPosList == null) { 625 mHighlightPosList = new ArrayList<CharPos>(); 626 } 627 mHighlightPosList.add(pos); 628 } 629 630 /* 631 * To parse and store the Karaoke list 632 */ 633 private void readKaraoke() { 634 int entryCount = mParcel.readInt(); 635 636 for (int i = 0; i < entryCount; i++) { 637 int startTimeMs = mParcel.readInt(); 638 int endTimeMs = mParcel.readInt(); 639 int startChar = mParcel.readInt(); 640 int endChar = mParcel.readInt(); 641 Karaoke kara = new Karaoke(startTimeMs, endTimeMs, 642 startChar, endChar); 643 644 if (mKaraokeList == null) { 645 mKaraokeList = new ArrayList<Karaoke>(); 646 } 647 mKaraokeList.add(kara); 648 } 649 } 650 651 /* 652 * To parse and store HyperText list 653 */ 654 private void readHyperText() { 655 int startChar = mParcel.readInt(); 656 int endChar = mParcel.readInt(); 657 658 int len = mParcel.readInt(); 659 byte[] url = mParcel.createByteArray(); 660 final String urlString = new String(url, 0, len); 661 662 len = mParcel.readInt(); 663 byte[] alt = mParcel.createByteArray(); 664 final String altString = new String(alt, 0, len); 665 HyperText hyperText = new HyperText(startChar, endChar, urlString, altString); 666 667 668 if (mHyperTextList == null) { 669 mHyperTextList = new ArrayList<HyperText>(); 670 } 671 mHyperTextList.add(hyperText); 672 } 673 674 /* 675 * To parse and store blinking text list 676 */ 677 private void readBlinkingText() { 678 int startChar = mParcel.readInt(); 679 int endChar = mParcel.readInt(); 680 CharPos blinkingPos = new CharPos(startChar, endChar); 681 682 if (mBlinkingPosList == null) { 683 mBlinkingPosList = new ArrayList<CharPos>(); 684 } 685 mBlinkingPosList.add(blinkingPos); 686 } 687 688 /* 689 * To check whether the given key is valid. 690 * @param key the key to be checked. 691 * @return true if the key is a valid one. Otherwise, false. 692 */ 693 private boolean isValidKey(final int key) { 694 if (!((key >= FIRST_PUBLIC_KEY) && (key <= LAST_PUBLIC_KEY)) 695 && !((key >= FIRST_PRIVATE_KEY) && (key <= LAST_PRIVATE_KEY))) { 696 return false; 697 } 698 return true; 699 } 700 701 /* 702 * To check whether the given key is contained in this TimedText object. 703 * @param key the key to be checked. 704 * @return true if the key is contained in this TimedText object. 705 * Otherwise, false. 706 */ 707 private boolean containsKey(final int key) { 708 if (isValidKey(key) && mKeyObjectMap.containsKey(key)) { 709 return true; 710 } 711 return false; 712 } 713 714 /* 715 * @return a set of the keys contained in this TimedText object. 716 */ 717 private Set keySet() { 718 return mKeyObjectMap.keySet(); 719 } 720 721 /* 722 * To retrieve the object associated with the key. Caller must make sure 723 * the key is present using the containsKey method otherwise a 724 * RuntimeException will occur. 725 * @param key the key used to retrieve the object. 726 * @return an object. The object could be 1) an instance of Integer; 2) a 727 * List of CharPos, Karaoke, Font, Style, and HyperText, or 3) an instance of 728 * Justification. 729 */ 730 private Object getObject(final int key) { 731 if (containsKey(key)) { 732 return mKeyObjectMap.get(key); 733 } else { 734 throw new IllegalArgumentException("Invalid key: " + key); 735 } 736 } 737} 738