Home | History | Annotate | Download | only in media
      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