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