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