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