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