mirror of
https://github.com/androidx/media.git
synced 2025-05-06 07:00:19 +08:00
Parsing embedded TTML styling.
This commit is contained in:
parent
95fcb3b411
commit
ab66dfa7c2
@ -0,0 +1,28 @@
|
|||||||
|
<tt xmlns:ttm="http://www.w3.org/2006/10/ttaf1#metadata" xmlns:ttp="http://www.w3.org/2006/10/ttaf1#parameter"
|
||||||
|
xmlns:tts="http://www.w3.org/2006/10/ttaf1#style"
|
||||||
|
xmlns="http://www.w3.org/ns/ttml"
|
||||||
|
xmlns="http://www.w3.org/2006/10/ttaf1">
|
||||||
|
<head>
|
||||||
|
<styling>
|
||||||
|
<style id="s0"
|
||||||
|
tts:backgroundColor="blue"
|
||||||
|
tts:color="black"
|
||||||
|
tts:fontWeight="bold" />
|
||||||
|
<style id="s1"
|
||||||
|
tts:backgroundColor="black"
|
||||||
|
tts:color="red"
|
||||||
|
tts:fontFamily="sansSerif"
|
||||||
|
tts:fontStyle="italic"
|
||||||
|
tts:textDecoration="lineThrough" />
|
||||||
|
<!-- multiple ids defined -->
|
||||||
|
<style style="s0 s1" id="s2"
|
||||||
|
tts:fontFamily="serif"
|
||||||
|
tts:backgroundColor="red" />
|
||||||
|
</styling>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div>
|
||||||
|
<p style="s2" begin="10s" end="18s">text 1</p>
|
||||||
|
</div>
|
||||||
|
</body>
|
||||||
|
</tt>
|
@ -0,0 +1,30 @@
|
|||||||
|
<tt xmlns="http://www.w3.org/ns/ttml"
|
||||||
|
xmlns="http://www.w3.org/2006/10/ttaf1"
|
||||||
|
xmlns:ttp="http://www.w3.org/2006/10/ttaf1#parameter"
|
||||||
|
xmlns:tts="http://www.w3.org/2006/10/ttaf1#style"
|
||||||
|
xmlns:ttm="http://www.w3.org/2006/10/ttaf1#metadata">
|
||||||
|
<head>
|
||||||
|
<styling>
|
||||||
|
<style id="s0"
|
||||||
|
tts:fontWeight="bold"
|
||||||
|
tts:fontStyle="italic"
|
||||||
|
tts:fontFamily="serif"
|
||||||
|
tts:textDecoration="underline"
|
||||||
|
tts:backgroundColor="blue"
|
||||||
|
tts:color="yellow"/>
|
||||||
|
</styling>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div>
|
||||||
|
<p style="s0" begin="10s" end="18s">text 1</p>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<p style="s0" begin="20s" end="28s"
|
||||||
|
tts:fontWeight="normal"
|
||||||
|
tts:fontFamily="sansSerif"
|
||||||
|
tts:backgroundColor="red"
|
||||||
|
tts:color="yellow"
|
||||||
|
>text 2</p>
|
||||||
|
</div>
|
||||||
|
</body>
|
||||||
|
</tt>
|
@ -0,0 +1,35 @@
|
|||||||
|
<tt xmlns="http://www.w3.org/ns/ttml"
|
||||||
|
xmlns="http://www.w3.org/2006/10/ttaf1"
|
||||||
|
xmlns:ttp="http://www.w3.org/2006/10/ttaf1#parameter"
|
||||||
|
xmlns:tts="http://www.w3.org/2006/10/ttaf1#style"
|
||||||
|
xmlns:ttm="http://www.w3.org/2006/10/ttaf1#metadata">
|
||||||
|
<head>
|
||||||
|
<styling>
|
||||||
|
<style id="s0"
|
||||||
|
tts:fontWeight="bold"
|
||||||
|
tts:fontStyle="italic"
|
||||||
|
tts:fontFamily="serif"
|
||||||
|
tts:textDecoration="underline"
|
||||||
|
tts:backgroundColor="blue"
|
||||||
|
tts:color="yellow"/>
|
||||||
|
</styling>
|
||||||
|
</head>
|
||||||
|
<body tts:textAlign="center">
|
||||||
|
<div tts:fontWeight="normal"
|
||||||
|
tts:fontStyle="normal"
|
||||||
|
tts:fontFamily="sansSerif"
|
||||||
|
tts:textDecoration="lineThrough"
|
||||||
|
tts:backgroundColor="red"
|
||||||
|
tts:color="lime">
|
||||||
|
<p begin="10s" end="18s">text 1</p>
|
||||||
|
</div>
|
||||||
|
<div tts:fontWeight="normal"
|
||||||
|
tts:fontStyle="normal"
|
||||||
|
tts:fontFamily="sansSerif"
|
||||||
|
tts:textDecoration="lineThrough"
|
||||||
|
tts:backgroundColor="red"
|
||||||
|
tts:color="lime">
|
||||||
|
<p style="s0" begin="20s" end="28s">text 2</p>
|
||||||
|
</div>
|
||||||
|
</body>
|
||||||
|
</tt>
|
@ -0,0 +1,45 @@
|
|||||||
|
<tt xmlns:ttm="http://www.w3.org/2006/10/ttaf1#metadata" xmlns:ttp="http://www.w3.org/2006/10/ttaf1#parameter"
|
||||||
|
xmlns:tts="http://www.w3.org/2006/10/ttaf1#style"
|
||||||
|
xmlns="http://www.w3.org/ns/ttml"
|
||||||
|
xmlns="http://www.w3.org/2006/10/ttaf1">
|
||||||
|
<head>
|
||||||
|
<styling>
|
||||||
|
<style id="s0"
|
||||||
|
tts:backgroundColor="blue"
|
||||||
|
tts:color="black"
|
||||||
|
tts:fontWeight="bold" />
|
||||||
|
<style id="s1"
|
||||||
|
tts:backgroundColor="black"
|
||||||
|
tts:color="red"
|
||||||
|
tts:fontFamily="sansSerif"
|
||||||
|
tts:fontStyle="italic"
|
||||||
|
tts:textDecoration="lineThrough" />
|
||||||
|
<style id="s2"
|
||||||
|
tts:backgroundColor="red" />
|
||||||
|
<style id="s3"
|
||||||
|
tts:backgroundColor="green"
|
||||||
|
tts:textDecoration="lineThrough" />
|
||||||
|
</styling>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div>
|
||||||
|
<p style="s0 s1" begin="10s" end="18s" tts:color="yellow">text 1</p>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<p style="s0 s1" begin="20s" end="28s">text 1</p>
|
||||||
|
</div>
|
||||||
|
<div tts:color="yellow" tts:textDecoration="underline" tts:fontStyle="italic" tts:fontFamily="sansSerifInline">
|
||||||
|
<p style="s2 s3" begin="30s" end="38s">text 1</p>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<!-- empty style attribute -->
|
||||||
|
<p style=" " begin="40s" end="48s">text 1</p>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<p style="not_existing" begin="50s" end="58s">text 1</p>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<p style="not_existing s2" begin="60s" end="68s">text 1</p>
|
||||||
|
</div>
|
||||||
|
</body>
|
||||||
|
</tt>
|
22
library/src/androidTest/assets/ttml/inherit_style.xml
Normal file
22
library/src/androidTest/assets/ttml/inherit_style.xml
Normal file
@ -0,0 +1,22 @@
|
|||||||
|
<tt xmlns="http://www.w3.org/ns/ttml"
|
||||||
|
xmlns="http://www.w3.org/2006/10/ttaf1"
|
||||||
|
xmlns:ttp="http://www.w3.org/2006/10/ttaf1#parameter"
|
||||||
|
xmlns:tts="http://www.w3.org/2006/10/ttaf1#style"
|
||||||
|
xmlns:ttm="http://www.w3.org/2006/10/ttaf1#metadata">
|
||||||
|
<head>
|
||||||
|
<styling>
|
||||||
|
<style id="s0"
|
||||||
|
tts:fontWeight="bold"
|
||||||
|
tts:fontStyle="italic"
|
||||||
|
tts:fontFamily="serif"
|
||||||
|
tts:textDecoration="underline"
|
||||||
|
tts:backgroundColor="blue"
|
||||||
|
tts:color="yellow"/>
|
||||||
|
</styling>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div>
|
||||||
|
<p style="s0" begin="10s" end="18s">text 1</p>
|
||||||
|
</div>
|
||||||
|
</body>
|
||||||
|
</tt>
|
@ -0,0 +1,25 @@
|
|||||||
|
<tt xmlns="http://www.w3.org/ns/ttml"
|
||||||
|
xmlns="http://www.w3.org/2006/10/ttaf1"
|
||||||
|
xmlns:ttp="http://www.w3.org/2006/10/ttaf1#parameter"
|
||||||
|
xmlns:tts="http://www.w3.org/2006/10/ttaf1#style"
|
||||||
|
xmlns:ttm="http://www.w3.org/2006/10/ttaf1#metadata">
|
||||||
|
<body>
|
||||||
|
<div>
|
||||||
|
<p begin="10s" end="18s"
|
||||||
|
tts:fontWeight="bold"
|
||||||
|
tts:fontStyle="italic"
|
||||||
|
tts:fontFamily="serif"
|
||||||
|
tts:textDecoration="underline"
|
||||||
|
tts:backgroundColor="blue"
|
||||||
|
tts:color="yellow">text 1</p>
|
||||||
|
</div>
|
||||||
|
<div tts:fontWeight="normal"
|
||||||
|
tts:fontStyle="italic"
|
||||||
|
tts:fontFamily="sansSerif"
|
||||||
|
tts:textDecoration="lineThrough"
|
||||||
|
tts:backgroundColor="cyan"
|
||||||
|
tts:color="lime">
|
||||||
|
<p begin="20s" end="28s">text 2</p>
|
||||||
|
</div>
|
||||||
|
</body>
|
||||||
|
</tt>
|
24
library/src/androidTest/assets/ttml/instance_creation.xml
Normal file
24
library/src/androidTest/assets/ttml/instance_creation.xml
Normal file
@ -0,0 +1,24 @@
|
|||||||
|
<tt xmlns:ttm="http://www.w3.org/2006/10/ttaf1#metadata" xmlns:ttp="http://www.w3.org/2006/10/ttaf1#parameter"
|
||||||
|
xmlns:tts="http://www.w3.org/2006/10/ttaf1#style"
|
||||||
|
xmlns="http://www.w3.org/ns/ttml"
|
||||||
|
xmlns="http://www.w3.org/2006/10/ttaf1">
|
||||||
|
<head>
|
||||||
|
<styling>
|
||||||
|
<style id="s0"
|
||||||
|
tts:color="blue"/>
|
||||||
|
<style id="s1"
|
||||||
|
tts:backgroundColor="red"/>
|
||||||
|
</styling>
|
||||||
|
</head>
|
||||||
|
<body style="s0">
|
||||||
|
<div>
|
||||||
|
<p style="s0" begin="10s" end="18s">text 1</p>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<p style="s0" begin="20s" end="28s">text <span style="s0">2</span></p>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<p style="s1" begin="20s" end="28s">text <span style="s1">3</span></p>
|
||||||
|
</div>
|
||||||
|
</body>
|
||||||
|
</tt>
|
@ -0,0 +1,21 @@
|
|||||||
|
<tt xmlns:ttm="http://www.w3.org/2006/10/ttaf1#metadata" xmlns:ttp="http://www.w3.org/2006/10/ttaf1#parameter"
|
||||||
|
xmlns:tts="http://www.w3.org/2006/10/ttaf1#style"
|
||||||
|
xmlns="http://www.w3.org/ns/ttml"
|
||||||
|
xmlns="http://www.w3.org/2006/10/ttaf1">
|
||||||
|
<head>
|
||||||
|
<styling>
|
||||||
|
<style id="s0"
|
||||||
|
tts:textDecoration="underline"/>
|
||||||
|
<style id="s1"
|
||||||
|
tts:textDecoration="lineThrough"/>
|
||||||
|
</styling>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div>
|
||||||
|
<p style="s0" begin="10s" end="18s" tts:textDecoration="noUnderline">text 1</p>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<p style="s1" begin="20s" end="28s" tts:textDecoration="noLineThrough">text 1</p>
|
||||||
|
</div>
|
||||||
|
</body>
|
||||||
|
</tt>
|
@ -0,0 +1,19 @@
|
|||||||
|
<tt xmlns="http://www.w3.org/ns/ttml"
|
||||||
|
xmlns="http://www.w3.org/2006/10/ttaf1"
|
||||||
|
xmlns:ttp="http://www.w3.org/2006/10/ttaf1#parameter"
|
||||||
|
xmlns:tts="http://www.w3.org/2006/10/ttaf1#style"
|
||||||
|
xmlns:ttm="http://www.w3.org/2006/10/ttaf1#metadata">
|
||||||
|
<body>
|
||||||
|
<div>
|
||||||
|
<p begin="10s" end="18s"
|
||||||
|
tts:fontWeight="bold"
|
||||||
|
tts:fontStyle="italic"
|
||||||
|
tts:fontFamily="serif"
|
||||||
|
tts:textDecoration="underline"
|
||||||
|
tts:backgroundColor="blue"
|
||||||
|
tts:color="yellow">
|
||||||
|
<span>text 1</span>
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</body>
|
||||||
|
</tt>
|
@ -0,0 +1,353 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (C) 2014 The Android Open Source Project
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
package com.google.android.exoplayer.text.ttml;
|
||||||
|
|
||||||
|
import android.graphics.Color;
|
||||||
|
import android.test.InstrumentationTestCase;
|
||||||
|
import android.text.Layout;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.io.InputStream;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Unit test for {@link TtmlParser}.
|
||||||
|
*/
|
||||||
|
public final class TtmlParserTest extends InstrumentationTestCase {
|
||||||
|
|
||||||
|
private static final String INLINE_ATTRIBUTES_TTML_FILE =
|
||||||
|
"ttml/inline_style_attributes.xml";
|
||||||
|
private static final String INHERIT_STYLE_TTML_FILE =
|
||||||
|
"ttml/inherit_style.xml";
|
||||||
|
private static final String INHERIT_STYLE_OVERRIDE_TTML_FILE =
|
||||||
|
"ttml/inherit_and_override_style.xml";
|
||||||
|
private static final String INHERIT_GLOBAL_AND_PARENT_TTML_FILE =
|
||||||
|
"ttml/inherit_global_and_parent.xml";
|
||||||
|
private static final String NON_INHERTABLE_PROPERTIES_TTML_FILE =
|
||||||
|
"ttml/non_inheritable_properties.xml";
|
||||||
|
private static final String INHERIT_MULTIPLE_STYLES_TTML_FILE =
|
||||||
|
"ttml/inherit_multiple_styles.xml";
|
||||||
|
private static final String CHAIN_MULTIPLE_STYLES_TTML_FILE =
|
||||||
|
"ttml/chain_multiple_styles.xml";
|
||||||
|
private static final String NO_UNDERLINE_LINETHROUGH_TTML_FILE =
|
||||||
|
"ttml/no_underline_linethrough.xml";
|
||||||
|
private static final String INSTANCE_CREATION_TTML_FILE =
|
||||||
|
"ttml/instance_creation.xml";
|
||||||
|
|
||||||
|
public void testInlineAttributes() throws IOException {
|
||||||
|
TtmlSubtitle subtitle = getSubtitle(INLINE_ATTRIBUTES_TTML_FILE);
|
||||||
|
assertEquals(4, subtitle.getEventTimeCount());
|
||||||
|
|
||||||
|
TtmlNode root = subtitle.getRoot();
|
||||||
|
|
||||||
|
TtmlNode body = queryChildrenForTag(root, TtmlNode.TAG_BODY, 0);
|
||||||
|
TtmlNode firstDiv = queryChildrenForTag(body, TtmlNode.TAG_DIV, 0);
|
||||||
|
TtmlStyle firstPStyle = queryChildrenForTag(firstDiv, TtmlNode.TAG_P, 0).style;
|
||||||
|
assertEquals(Color.parseColor("yellow"), firstPStyle.getColor());
|
||||||
|
assertEquals(Color.parseColor("blue"), firstPStyle.getBackgroundColor());
|
||||||
|
assertEquals("serif", firstPStyle.getFontFamily());
|
||||||
|
assertEquals(TtmlStyle.STYLE_BOLD_ITALIC, firstPStyle.getStyle());
|
||||||
|
assertTrue(firstPStyle.isUnderline());
|
||||||
|
}
|
||||||
|
|
||||||
|
public void testInheritInlineAttributes() throws IOException {
|
||||||
|
|
||||||
|
TtmlSubtitle subtitle = getSubtitle(INLINE_ATTRIBUTES_TTML_FILE);
|
||||||
|
assertEquals(4, subtitle.getEventTimeCount());
|
||||||
|
|
||||||
|
TtmlNode root = subtitle.getRoot();
|
||||||
|
// inherite inline attributes
|
||||||
|
TtmlNode body = queryChildrenForTag(root, TtmlNode.TAG_BODY, 0);
|
||||||
|
TtmlNode secondDiv = queryChildrenForTag(body, TtmlNode.TAG_DIV, 1);
|
||||||
|
TtmlStyle secondPStyle = queryChildrenForTag(secondDiv, TtmlNode.TAG_P, 0).style;
|
||||||
|
assertEquals(Color.parseColor("lime"), secondPStyle.getColor());
|
||||||
|
assertFalse(secondPStyle.hasBackgroundColorSpecified());
|
||||||
|
assertEquals(0, secondPStyle.getBackgroundColor());
|
||||||
|
assertEquals("sansSerif", secondPStyle.getFontFamily());
|
||||||
|
assertEquals(TtmlStyle.STYLE_ITALIC, secondPStyle.getStyle());
|
||||||
|
assertTrue(secondPStyle.isLinethrough());
|
||||||
|
}
|
||||||
|
|
||||||
|
public void testInheritGlobalStyle() throws IOException {
|
||||||
|
TtmlSubtitle subtitle = getSubtitle(INHERIT_STYLE_TTML_FILE);
|
||||||
|
assertEquals(2, subtitle.getEventTimeCount());
|
||||||
|
|
||||||
|
TtmlNode root = subtitle.getRoot();
|
||||||
|
|
||||||
|
TtmlNode body = queryChildrenForTag(root, TtmlNode.TAG_BODY, 0);
|
||||||
|
TtmlNode firstDiv = queryChildrenForTag(body, TtmlNode.TAG_DIV, 0);
|
||||||
|
TtmlStyle firstPStyle = queryChildrenForTag(firstDiv, TtmlNode.TAG_P, 0).style;
|
||||||
|
assertEquals(Color.parseColor("yellow"), firstPStyle.getColor());
|
||||||
|
assertEquals(Color.parseColor("blue"), firstPStyle.getBackgroundColor());
|
||||||
|
assertEquals("serif", firstPStyle.getFontFamily());
|
||||||
|
assertEquals(TtmlStyle.STYLE_BOLD_ITALIC, firstPStyle.getStyle());
|
||||||
|
assertTrue(firstPStyle.isUnderline());
|
||||||
|
}
|
||||||
|
|
||||||
|
public void testInheritGlobalStyleOverriddenByInlineAttributes() throws IOException {
|
||||||
|
TtmlSubtitle subtitle = getSubtitle(INHERIT_STYLE_OVERRIDE_TTML_FILE);
|
||||||
|
assertEquals(4, subtitle.getEventTimeCount());
|
||||||
|
|
||||||
|
TtmlNode root = subtitle.getRoot();
|
||||||
|
|
||||||
|
// first pNode inherits global style
|
||||||
|
TtmlNode body = queryChildrenForTag(root, TtmlNode.TAG_BODY, 0);
|
||||||
|
TtmlNode firstDiv = queryChildrenForTag(body, TtmlNode.TAG_DIV, 0);
|
||||||
|
TtmlStyle firstPStyle = queryChildrenForTag(firstDiv, TtmlNode.TAG_P, 0).style;
|
||||||
|
assertEquals(Color.parseColor("yellow"), firstPStyle.getColor());
|
||||||
|
assertEquals(Color.parseColor("blue"), firstPStyle.getBackgroundColor());
|
||||||
|
assertEquals("serif", firstPStyle.getFontFamily());
|
||||||
|
assertEquals(TtmlStyle.STYLE_BOLD_ITALIC, firstPStyle.getStyle());
|
||||||
|
assertTrue(firstPStyle.isUnderline());
|
||||||
|
|
||||||
|
// second pNode inherits global style and overrides with attribute
|
||||||
|
TtmlNode secondDiv = queryChildrenForTag(body, TtmlNode.TAG_DIV, 1);
|
||||||
|
TtmlStyle secondPStyle = queryChildrenForTag(secondDiv, TtmlNode.TAG_P, 0).style;
|
||||||
|
assertEquals(Color.parseColor("yellow"), secondPStyle.getColor());
|
||||||
|
assertEquals(Color.parseColor("red"), secondPStyle.getBackgroundColor());
|
||||||
|
assertEquals("sansSerif", secondPStyle.getFontFamily());
|
||||||
|
assertEquals(TtmlStyle.STYLE_ITALIC, secondPStyle.getStyle());
|
||||||
|
assertTrue(secondPStyle.isUnderline());
|
||||||
|
}
|
||||||
|
|
||||||
|
public void testInheritGlobalAndParent() throws IOException {
|
||||||
|
TtmlSubtitle subtitle = getSubtitle(INHERIT_GLOBAL_AND_PARENT_TTML_FILE);
|
||||||
|
assertEquals(4, subtitle.getEventTimeCount());
|
||||||
|
|
||||||
|
TtmlNode root = subtitle.getRoot();
|
||||||
|
|
||||||
|
// first pNode inherits parent style
|
||||||
|
TtmlNode body = queryChildrenForTag(root, TtmlNode.TAG_BODY, 0);
|
||||||
|
TtmlNode firstDiv = queryChildrenForTag(body, TtmlNode.TAG_DIV, 0);
|
||||||
|
TtmlStyle firstPStyle = queryChildrenForTag(firstDiv, TtmlNode.TAG_P, 0).style;
|
||||||
|
|
||||||
|
assertFalse(firstPStyle.hasBackgroundColorSpecified());
|
||||||
|
assertEquals(0, firstPStyle.getBackgroundColor());
|
||||||
|
|
||||||
|
assertEquals(Color.parseColor("lime"), firstPStyle.getColor());
|
||||||
|
assertEquals("sansSerif", firstPStyle.getFontFamily());
|
||||||
|
assertEquals(TtmlStyle.STYLE_NORMAL, firstPStyle.getStyle());
|
||||||
|
assertTrue(firstPStyle.isLinethrough());
|
||||||
|
|
||||||
|
// second pNode inherits parent style and overrides with global style
|
||||||
|
TtmlNode secondDiv = queryChildrenForTag(body, TtmlNode.TAG_DIV, 1);
|
||||||
|
TtmlStyle secondPStyle = queryChildrenForTag(secondDiv, TtmlNode.TAG_P, 0).style;
|
||||||
|
// attributes overridden by global style
|
||||||
|
assertEquals(Color.parseColor("blue"), secondPStyle.getBackgroundColor());
|
||||||
|
assertEquals(Color.parseColor("yellow"), secondPStyle.getColor());
|
||||||
|
assertEquals(TtmlStyle.STYLE_BOLD_ITALIC, secondPStyle.getStyle());
|
||||||
|
assertEquals("serif", secondPStyle.getFontFamily());
|
||||||
|
assertTrue(secondPStyle.isUnderline());
|
||||||
|
assertEquals(Layout.Alignment.ALIGN_CENTER, secondPStyle.getTextAlign());
|
||||||
|
}
|
||||||
|
|
||||||
|
public void testNonInheritablePropertiesAreNotInherited() throws IOException {
|
||||||
|
TtmlSubtitle subtitle = getSubtitle(NON_INHERTABLE_PROPERTIES_TTML_FILE);
|
||||||
|
assertEquals(2, subtitle.getEventTimeCount());
|
||||||
|
|
||||||
|
TtmlNode root = subtitle.getRoot();
|
||||||
|
|
||||||
|
TtmlNode body = queryChildrenForTag(root, TtmlNode.TAG_BODY, 0);
|
||||||
|
TtmlNode firstDiv = queryChildrenForTag(body, TtmlNode.TAG_DIV, 0);
|
||||||
|
TtmlNode firstPStyle = queryChildrenForTag(firstDiv, TtmlNode.TAG_P, 0);
|
||||||
|
TtmlStyle spanStyle = queryChildrenForTag(firstPStyle, TtmlNode.TAG_SPAN, 0).style;
|
||||||
|
|
||||||
|
assertFalse("background color must not be inherited from a context node",
|
||||||
|
spanStyle.hasBackgroundColorSpecified());
|
||||||
|
}
|
||||||
|
|
||||||
|
public void testInheritMultipleStyles() throws IOException {
|
||||||
|
TtmlSubtitle subtitle = getSubtitle(INHERIT_MULTIPLE_STYLES_TTML_FILE);
|
||||||
|
assertEquals(12, subtitle.getEventTimeCount());
|
||||||
|
|
||||||
|
TtmlNode root = subtitle.getRoot();
|
||||||
|
|
||||||
|
TtmlNode body = queryChildrenForTag(root, TtmlNode.TAG_BODY, 0);
|
||||||
|
TtmlNode firstDiv = queryChildrenForTag(body, TtmlNode.TAG_DIV, 0);
|
||||||
|
TtmlStyle firstPStyle = queryChildrenForTag(firstDiv, TtmlNode.TAG_P, 0).style;
|
||||||
|
|
||||||
|
assertEquals(Color.parseColor("blue"), firstPStyle.getBackgroundColor());
|
||||||
|
assertEquals(Color.parseColor("yellow"), firstPStyle.getColor());
|
||||||
|
assertEquals("sansSerif", firstPStyle.getFontFamily());
|
||||||
|
assertEquals(TtmlStyle.STYLE_BOLD_ITALIC, firstPStyle.getStyle());
|
||||||
|
assertTrue(firstPStyle.isLinethrough());
|
||||||
|
}
|
||||||
|
|
||||||
|
public void testInheritMultipleStylesWithoutLocalAttributes() throws IOException {
|
||||||
|
TtmlSubtitle subtitle = getSubtitle(INHERIT_MULTIPLE_STYLES_TTML_FILE);
|
||||||
|
assertEquals(12, subtitle.getEventTimeCount());
|
||||||
|
|
||||||
|
TtmlNode root = subtitle.getRoot();
|
||||||
|
|
||||||
|
TtmlNode body = queryChildrenForTag(root, TtmlNode.TAG_BODY, 0);
|
||||||
|
TtmlNode secondDiv = queryChildrenForTag(body, TtmlNode.TAG_DIV, 1);
|
||||||
|
TtmlStyle firstPStyle = queryChildrenForTag(secondDiv, TtmlNode.TAG_P, 0).style;
|
||||||
|
|
||||||
|
assertEquals(Color.parseColor("blue"), firstPStyle.getBackgroundColor());
|
||||||
|
assertEquals(Color.parseColor("black"), firstPStyle.getColor());
|
||||||
|
assertEquals("sansSerif", firstPStyle.getFontFamily());
|
||||||
|
assertEquals(TtmlStyle.STYLE_BOLD_ITALIC, firstPStyle.getStyle());
|
||||||
|
assertTrue(firstPStyle.isLinethrough());
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
public void testMergeMultipleStylesWithParentStyle() throws IOException {
|
||||||
|
TtmlSubtitle subtitle = getSubtitle(INHERIT_MULTIPLE_STYLES_TTML_FILE);
|
||||||
|
assertEquals(12, subtitle.getEventTimeCount());
|
||||||
|
|
||||||
|
TtmlNode root = subtitle.getRoot();
|
||||||
|
|
||||||
|
TtmlNode body = queryChildrenForTag(root, TtmlNode.TAG_BODY, 0);
|
||||||
|
TtmlNode thirdDiv = queryChildrenForTag(body, TtmlNode.TAG_DIV, 2);
|
||||||
|
TtmlStyle firstPStyle = queryChildrenForTag(thirdDiv, TtmlNode.TAG_P, 0).style;
|
||||||
|
|
||||||
|
// inherit from first global style
|
||||||
|
assertEquals(Color.parseColor("red"), firstPStyle.getBackgroundColor());
|
||||||
|
// inherit from second global style
|
||||||
|
assertTrue(firstPStyle.isLinethrough());
|
||||||
|
// inherited from parent node
|
||||||
|
assertEquals("sansSerifInline", firstPStyle.getFontFamily());
|
||||||
|
assertEquals(TtmlStyle.STYLE_ITALIC, firstPStyle.getStyle());
|
||||||
|
assertTrue(firstPStyle.isUnderline());
|
||||||
|
assertEquals(Color.parseColor("yellow"), firstPStyle.getColor());
|
||||||
|
}
|
||||||
|
|
||||||
|
public void testEmptyStyleAttribute() throws IOException {
|
||||||
|
TtmlSubtitle subtitle = getSubtitle(INHERIT_MULTIPLE_STYLES_TTML_FILE);
|
||||||
|
assertEquals(12, subtitle.getEventTimeCount());
|
||||||
|
|
||||||
|
TtmlNode root = subtitle.getRoot();
|
||||||
|
TtmlNode body = queryChildrenForTag(root, TtmlNode.TAG_BODY, 0);
|
||||||
|
TtmlNode fourthDiv = queryChildrenForTag(body, TtmlNode.TAG_DIV, 3);
|
||||||
|
|
||||||
|
// no styles specified
|
||||||
|
assertNull(queryChildrenForTag(fourthDiv, TtmlNode.TAG_P, 0).style);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void testNonexistingStyleId() throws IOException {
|
||||||
|
TtmlSubtitle subtitle = getSubtitle(INHERIT_MULTIPLE_STYLES_TTML_FILE);
|
||||||
|
assertEquals(12, subtitle.getEventTimeCount());
|
||||||
|
|
||||||
|
TtmlNode root = subtitle.getRoot();
|
||||||
|
TtmlNode body = queryChildrenForTag(root, TtmlNode.TAG_BODY, 0);
|
||||||
|
TtmlNode fifthDiv = queryChildrenForTag(body, TtmlNode.TAG_DIV, 4);
|
||||||
|
|
||||||
|
// no styles specified
|
||||||
|
assertNull(queryChildrenForTag(fifthDiv, TtmlNode.TAG_P, 0).style);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void testNonExistingAndExistingStyleId() throws IOException {
|
||||||
|
TtmlSubtitle subtitle = getSubtitle(INHERIT_MULTIPLE_STYLES_TTML_FILE);
|
||||||
|
assertEquals(12, subtitle.getEventTimeCount());
|
||||||
|
|
||||||
|
TtmlNode root = subtitle.getRoot();
|
||||||
|
TtmlNode body = queryChildrenForTag(root, TtmlNode.TAG_BODY, 0);
|
||||||
|
TtmlNode sixthDiv = queryChildrenForTag(body, TtmlNode.TAG_DIV, 5);
|
||||||
|
|
||||||
|
// no styles specified
|
||||||
|
TtmlStyle style = queryChildrenForTag(sixthDiv, TtmlNode.TAG_P, 0).style;
|
||||||
|
assertNotNull(style);
|
||||||
|
assertEquals(Color.RED, style.getBackgroundColor());
|
||||||
|
}
|
||||||
|
|
||||||
|
public void testMultipleChaining() throws IOException {
|
||||||
|
TtmlSubtitle subtitle = getSubtitle(CHAIN_MULTIPLE_STYLES_TTML_FILE);
|
||||||
|
assertEquals(2, subtitle.getEventTimeCount());
|
||||||
|
|
||||||
|
TtmlNode root = subtitle.getRoot();
|
||||||
|
TtmlNode body = queryChildrenForTag(root, TtmlNode.TAG_BODY, 0);
|
||||||
|
TtmlNode div = queryChildrenForTag(body, TtmlNode.TAG_DIV, 0);
|
||||||
|
|
||||||
|
// no styles specified
|
||||||
|
TtmlStyle style = queryChildrenForTag(div, TtmlNode.TAG_P, 0).style;
|
||||||
|
assertEquals("serif", style.getFontFamily());
|
||||||
|
assertEquals(Color.RED, style.getBackgroundColor());
|
||||||
|
assertEquals(Color.BLACK, style.getColor());
|
||||||
|
assertEquals(TtmlStyle.STYLE_BOLD_ITALIC, style.getStyle());
|
||||||
|
assertTrue(style.isLinethrough());
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
public void testNoUnderline() throws IOException {
|
||||||
|
TtmlSubtitle subtitle = getSubtitle(NO_UNDERLINE_LINETHROUGH_TTML_FILE);
|
||||||
|
assertEquals(4, subtitle.getEventTimeCount());
|
||||||
|
|
||||||
|
TtmlNode root = subtitle.getRoot();
|
||||||
|
TtmlNode body = queryChildrenForTag(root, TtmlNode.TAG_BODY, 0);
|
||||||
|
TtmlNode div = queryChildrenForTag(body, TtmlNode.TAG_DIV, 0);
|
||||||
|
|
||||||
|
TtmlStyle style = queryChildrenForTag(div, TtmlNode.TAG_P, 0).style;
|
||||||
|
assertFalse("noUnderline from inline attribute expected", style.isUnderline());
|
||||||
|
}
|
||||||
|
|
||||||
|
public void testNoLinethrough() throws IOException {
|
||||||
|
TtmlSubtitle subtitle = getSubtitle(NO_UNDERLINE_LINETHROUGH_TTML_FILE);
|
||||||
|
assertEquals(4, subtitle.getEventTimeCount());
|
||||||
|
|
||||||
|
TtmlNode root = subtitle.getRoot();
|
||||||
|
TtmlNode body = queryChildrenForTag(root, TtmlNode.TAG_BODY, 0);
|
||||||
|
TtmlNode div = queryChildrenForTag(body, TtmlNode.TAG_DIV, 1);
|
||||||
|
|
||||||
|
TtmlStyle style = queryChildrenForTag(div, TtmlNode.TAG_P, 0).style;
|
||||||
|
assertFalse("noLineThrough from inline attribute expected in second pNode",
|
||||||
|
style.isLinethrough());
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public void testOnlySingleInstance() throws IOException {
|
||||||
|
TtmlSubtitle subtitle = getSubtitle(INSTANCE_CREATION_TTML_FILE);
|
||||||
|
assertEquals(4, subtitle.getEventTimeCount());
|
||||||
|
|
||||||
|
TtmlNode root = subtitle.getRoot();
|
||||||
|
TtmlNode body = queryChildrenForTag(root, TtmlNode.TAG_BODY, 0);
|
||||||
|
TtmlNode firstDiv = queryChildrenForTag(body, TtmlNode.TAG_DIV, 0);
|
||||||
|
TtmlNode secondDiv = queryChildrenForTag(body, TtmlNode.TAG_DIV, 1);
|
||||||
|
TtmlNode thirdDiv = queryChildrenForTag(body, TtmlNode.TAG_DIV, 2);
|
||||||
|
|
||||||
|
TtmlNode firstP = queryChildrenForTag(firstDiv, TtmlNode.TAG_P, 0);
|
||||||
|
TtmlNode secondP = queryChildrenForTag(secondDiv, TtmlNode.TAG_P, 0);
|
||||||
|
TtmlNode secondSpan = queryChildrenForTag(secondP, TtmlNode.TAG_SPAN, 0);
|
||||||
|
TtmlNode thirdP = queryChildrenForTag(thirdDiv, TtmlNode.TAG_P, 0);
|
||||||
|
TtmlNode thirdSpan = queryChildrenForTag(secondP, TtmlNode.TAG_SPAN, 0);
|
||||||
|
|
||||||
|
// inherit the same instance down the tree if possible
|
||||||
|
assertSame(body.style, firstP.style);
|
||||||
|
assertSame(firstP.style, secondP.style);
|
||||||
|
assertSame(secondP.style, secondSpan.style);
|
||||||
|
|
||||||
|
// if a backgroundColor is involved it does not help
|
||||||
|
assertNotSame(thirdP.style.getInheritableStyle(), thirdSpan.style);
|
||||||
|
}
|
||||||
|
|
||||||
|
private TtmlNode queryChildrenForTag(TtmlNode node, String tag, int pos) {
|
||||||
|
int count = 0;
|
||||||
|
for (int i = 0; i < node.getChildCount(); i++) {
|
||||||
|
if (tag.equals(node.getChild(i).tag)) {
|
||||||
|
if (pos == count++) {
|
||||||
|
return node.getChild(i);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
private TtmlSubtitle getSubtitle(String file) throws IOException {
|
||||||
|
TtmlParser ttmlParser = new TtmlParser(false);
|
||||||
|
InputStream inputStream = getInstrumentation().getContext()
|
||||||
|
.getResources().getAssets().open(file);
|
||||||
|
|
||||||
|
return (TtmlSubtitle) ttmlParser.parse(inputStream);
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,155 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (C) 2014 The Android Open Source Project
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
package com.google.android.exoplayer.text.ttml;
|
||||||
|
|
||||||
|
import android.graphics.Color;
|
||||||
|
import android.test.InstrumentationTestCase;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Unit test for {@link TtmlStyle}.
|
||||||
|
*/
|
||||||
|
public final class TtmlStyleTest extends InstrumentationTestCase {
|
||||||
|
|
||||||
|
private static final String FONT_FAMILY = "serif";
|
||||||
|
private static final String ID = "id";
|
||||||
|
public static final int FOREGROUND_COLOR = Color.WHITE;
|
||||||
|
public static final int BACKGROUND_COLOR = Color.BLACK;
|
||||||
|
private TtmlStyle style;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setUp() throws Exception {
|
||||||
|
super.setUp();
|
||||||
|
style = new TtmlStyle();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void testInheritStyle() {
|
||||||
|
style.inherit(createAncestorStyle());
|
||||||
|
assertNull("id must not be inherited", style.getId());
|
||||||
|
assertTrue(style.isUnderline());
|
||||||
|
assertTrue(style.isLinethrough());
|
||||||
|
assertEquals(TtmlStyle.STYLE_BOLD_ITALIC, style.getStyle());
|
||||||
|
assertEquals(FONT_FAMILY, style.getFontFamily());
|
||||||
|
assertEquals(Color.WHITE, style.getColor());
|
||||||
|
assertFalse("do not inherit backgroundColor", style.hasBackgroundColorSpecified());
|
||||||
|
}
|
||||||
|
|
||||||
|
public void testChainStyle() {
|
||||||
|
style.chain(createAncestorStyle());
|
||||||
|
assertNull("id must not be inherited", style.getId());
|
||||||
|
assertTrue(style.isUnderline());
|
||||||
|
assertTrue(style.isLinethrough());
|
||||||
|
assertEquals(TtmlStyle.STYLE_BOLD_ITALIC, style.getStyle());
|
||||||
|
assertEquals(FONT_FAMILY, style.getFontFamily());
|
||||||
|
assertEquals(FOREGROUND_COLOR, style.getColor());
|
||||||
|
// do inherit backgroundColor when chaining
|
||||||
|
assertEquals("do not inherit backgroundColor when chaining",
|
||||||
|
BACKGROUND_COLOR, style.getBackgroundColor());
|
||||||
|
}
|
||||||
|
|
||||||
|
public void testGetInheritableStyle() {
|
||||||
|
// same instance as long as everything can be inherited
|
||||||
|
assertSame(style, style.getInheritableStyle());
|
||||||
|
style.inherit(createAncestorStyle());
|
||||||
|
assertSame(style, style.getInheritableStyle());
|
||||||
|
// after setting a property which is not inheritable
|
||||||
|
// we expect the inheritable style to be another instance
|
||||||
|
style.setBackgroundColor(0);
|
||||||
|
TtmlStyle inheritableStyle = style.getInheritableStyle();
|
||||||
|
assertNotSame(style, inheritableStyle);
|
||||||
|
// and subsequent call give always the same instance
|
||||||
|
assertSame(inheritableStyle, style.getInheritableStyle());
|
||||||
|
|
||||||
|
boolean exceptionThrown = false;
|
||||||
|
try {
|
||||||
|
// setting properties after calling getInheritableStyle gives an exception
|
||||||
|
style.setItalic(true);
|
||||||
|
} catch (IllegalStateException e) {
|
||||||
|
exceptionThrown = true;
|
||||||
|
}
|
||||||
|
assertTrue(exceptionThrown);
|
||||||
|
}
|
||||||
|
|
||||||
|
private TtmlStyle createAncestorStyle() {
|
||||||
|
TtmlStyle ancestor = new TtmlStyle();
|
||||||
|
ancestor.setId(ID);
|
||||||
|
ancestor.setItalic(true);
|
||||||
|
ancestor.setBold(true);
|
||||||
|
ancestor.setBackgroundColor(BACKGROUND_COLOR);
|
||||||
|
ancestor.setColor(FOREGROUND_COLOR);
|
||||||
|
ancestor.setLinethrough(true);
|
||||||
|
ancestor.setUnderline(true);
|
||||||
|
ancestor.setFontFamily(FONT_FAMILY);
|
||||||
|
return ancestor;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void testStyle() {
|
||||||
|
assertEquals(TtmlStyle.UNSPECIFIED, style.getStyle());
|
||||||
|
style.setItalic(true);
|
||||||
|
assertEquals(TtmlStyle.STYLE_ITALIC, style.getStyle());
|
||||||
|
style.setBold(true);
|
||||||
|
assertEquals(TtmlStyle.STYLE_BOLD_ITALIC, style.getStyle());
|
||||||
|
style.setItalic(false);
|
||||||
|
assertEquals(TtmlStyle.STYLE_BOLD, style.getStyle());
|
||||||
|
style.setBold(false);
|
||||||
|
assertEquals(TtmlStyle.STYLE_NORMAL, style.getStyle());
|
||||||
|
}
|
||||||
|
|
||||||
|
public void testLinethrough() {
|
||||||
|
assertFalse(style.isLinethrough());
|
||||||
|
style.setLinethrough(true);
|
||||||
|
assertTrue(style.isLinethrough());
|
||||||
|
style.setLinethrough(false);
|
||||||
|
assertFalse(style.isLinethrough());
|
||||||
|
}
|
||||||
|
|
||||||
|
public void testUnderline() {
|
||||||
|
assertFalse(style.isUnderline());
|
||||||
|
style.setUnderline(true);
|
||||||
|
assertTrue(style.isUnderline());
|
||||||
|
style.setUnderline(false);
|
||||||
|
assertFalse(style.isUnderline());
|
||||||
|
}
|
||||||
|
|
||||||
|
public void testFontFamily() {
|
||||||
|
assertNull(style.getFontFamily());
|
||||||
|
style.setFontFamily(FONT_FAMILY);
|
||||||
|
assertEquals(FONT_FAMILY, style.getFontFamily());
|
||||||
|
style.setFontFamily(null);
|
||||||
|
assertNull(style.getFontFamily());
|
||||||
|
}
|
||||||
|
|
||||||
|
public void testColor() {
|
||||||
|
assertFalse(style.hasColorSpecified());
|
||||||
|
style.setColor(Color.BLACK);
|
||||||
|
assertEquals(Color.BLACK, style.getColor());
|
||||||
|
assertTrue(style.hasColorSpecified());
|
||||||
|
}
|
||||||
|
|
||||||
|
public void testBackgroundColor() {
|
||||||
|
assertFalse(style.hasBackgroundColorSpecified());
|
||||||
|
style.setBackgroundColor(Color.BLACK);
|
||||||
|
assertEquals(Color.BLACK, style.getBackgroundColor());
|
||||||
|
assertTrue(style.hasBackgroundColorSpecified());
|
||||||
|
}
|
||||||
|
|
||||||
|
public void testId() {
|
||||||
|
assertNull(style.getId());
|
||||||
|
style.setId(ID);
|
||||||
|
assertEquals(ID, style.getId());
|
||||||
|
style.setId(null);
|
||||||
|
assertNull(style.getId());
|
||||||
|
}
|
||||||
|
}
|
@ -25,6 +25,7 @@ import com.google.android.exoplayer.extractor.mp4.PsshAtomUtil;
|
|||||||
import com.google.android.exoplayer.upstream.UriLoadable;
|
import com.google.android.exoplayer.upstream.UriLoadable;
|
||||||
import com.google.android.exoplayer.util.Assertions;
|
import com.google.android.exoplayer.util.Assertions;
|
||||||
import com.google.android.exoplayer.util.MimeTypes;
|
import com.google.android.exoplayer.util.MimeTypes;
|
||||||
|
import com.google.android.exoplayer.util.ParserUtil;
|
||||||
import com.google.android.exoplayer.util.UriUtil;
|
import com.google.android.exoplayer.util.UriUtil;
|
||||||
import com.google.android.exoplayer.util.Util;
|
import com.google.android.exoplayer.util.Util;
|
||||||
|
|
||||||
@ -120,16 +121,16 @@ public class MediaPresentationDescriptionParser extends DefaultHandler
|
|||||||
boolean seenFirstBaseUrl = false;
|
boolean seenFirstBaseUrl = false;
|
||||||
do {
|
do {
|
||||||
xpp.next();
|
xpp.next();
|
||||||
if (isStartTag(xpp, "BaseURL")) {
|
if (ParserUtil.isStartTag(xpp, "BaseURL")) {
|
||||||
if (!seenFirstBaseUrl) {
|
if (!seenFirstBaseUrl) {
|
||||||
baseUrl = parseBaseUrl(xpp, baseUrl);
|
baseUrl = parseBaseUrl(xpp, baseUrl);
|
||||||
seenFirstBaseUrl = true;
|
seenFirstBaseUrl = true;
|
||||||
}
|
}
|
||||||
} else if (isStartTag(xpp, "UTCTiming")) {
|
} else if (ParserUtil.isStartTag(xpp, "UTCTiming")) {
|
||||||
utcTiming = parseUtcTiming(xpp);
|
utcTiming = parseUtcTiming(xpp);
|
||||||
} else if (isStartTag(xpp, "Location")) {
|
} else if (ParserUtil.isStartTag(xpp, "Location")) {
|
||||||
location = xpp.nextText();
|
location = xpp.nextText();
|
||||||
} else if (isStartTag(xpp, "Period") && !seenEarlyAccessPeriod) {
|
} else if (ParserUtil.isStartTag(xpp, "Period") && !seenEarlyAccessPeriod) {
|
||||||
Pair<Period, Long> periodWithDurationMs = parsePeriod(xpp, baseUrl, nextPeriodStartMs);
|
Pair<Period, Long> periodWithDurationMs = parsePeriod(xpp, baseUrl, nextPeriodStartMs);
|
||||||
Period period = periodWithDurationMs.first;
|
Period period = periodWithDurationMs.first;
|
||||||
if (period.startMs == -1) {
|
if (period.startMs == -1) {
|
||||||
@ -146,7 +147,7 @@ public class MediaPresentationDescriptionParser extends DefaultHandler
|
|||||||
periods.add(period);
|
periods.add(period);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} while (!isEndTag(xpp, "MPD"));
|
} while (!ParserUtil.isEndTag(xpp, "MPD"));
|
||||||
|
|
||||||
if (!dynamic && durationMs == -1) {
|
if (!dynamic && durationMs == -1) {
|
||||||
// The manifest is static and doesn't define a duration. This is unexpected.
|
// The manifest is static and doesn't define a duration. This is unexpected.
|
||||||
@ -190,21 +191,21 @@ public class MediaPresentationDescriptionParser extends DefaultHandler
|
|||||||
boolean seenFirstBaseUrl = false;
|
boolean seenFirstBaseUrl = false;
|
||||||
do {
|
do {
|
||||||
xpp.next();
|
xpp.next();
|
||||||
if (isStartTag(xpp, "BaseURL")) {
|
if (ParserUtil.isStartTag(xpp, "BaseURL")) {
|
||||||
if (!seenFirstBaseUrl) {
|
if (!seenFirstBaseUrl) {
|
||||||
baseUrl = parseBaseUrl(xpp, baseUrl);
|
baseUrl = parseBaseUrl(xpp, baseUrl);
|
||||||
seenFirstBaseUrl = true;
|
seenFirstBaseUrl = true;
|
||||||
}
|
}
|
||||||
} else if (isStartTag(xpp, "AdaptationSet")) {
|
} else if (ParserUtil.isStartTag(xpp, "AdaptationSet")) {
|
||||||
adaptationSets.add(parseAdaptationSet(xpp, baseUrl, segmentBase));
|
adaptationSets.add(parseAdaptationSet(xpp, baseUrl, segmentBase));
|
||||||
} else if (isStartTag(xpp, "SegmentBase")) {
|
} else if (ParserUtil.isStartTag(xpp, "SegmentBase")) {
|
||||||
segmentBase = parseSegmentBase(xpp, baseUrl, null);
|
segmentBase = parseSegmentBase(xpp, baseUrl, null);
|
||||||
} else if (isStartTag(xpp, "SegmentList")) {
|
} else if (ParserUtil.isStartTag(xpp, "SegmentList")) {
|
||||||
segmentBase = parseSegmentList(xpp, baseUrl, null);
|
segmentBase = parseSegmentList(xpp, baseUrl, null);
|
||||||
} else if (isStartTag(xpp, "SegmentTemplate")) {
|
} else if (ParserUtil.isStartTag(xpp, "SegmentTemplate")) {
|
||||||
segmentBase = parseSegmentTemplate(xpp, baseUrl, null);
|
segmentBase = parseSegmentTemplate(xpp, baseUrl, null);
|
||||||
}
|
}
|
||||||
} while (!isEndTag(xpp, "Period"));
|
} while (!ParserUtil.isEndTag(xpp, "Period"));
|
||||||
|
|
||||||
return Pair.create(buildPeriod(id, startMs, adaptationSets), durationMs);
|
return Pair.create(buildPeriod(id, startMs, adaptationSets), durationMs);
|
||||||
}
|
}
|
||||||
@ -234,35 +235,35 @@ public class MediaPresentationDescriptionParser extends DefaultHandler
|
|||||||
boolean seenFirstBaseUrl = false;
|
boolean seenFirstBaseUrl = false;
|
||||||
do {
|
do {
|
||||||
xpp.next();
|
xpp.next();
|
||||||
if (isStartTag(xpp, "BaseURL")) {
|
if (ParserUtil.isStartTag(xpp, "BaseURL")) {
|
||||||
if (!seenFirstBaseUrl) {
|
if (!seenFirstBaseUrl) {
|
||||||
baseUrl = parseBaseUrl(xpp, baseUrl);
|
baseUrl = parseBaseUrl(xpp, baseUrl);
|
||||||
seenFirstBaseUrl = true;
|
seenFirstBaseUrl = true;
|
||||||
}
|
}
|
||||||
} else if (isStartTag(xpp, "ContentProtection")) {
|
} else if (ParserUtil.isStartTag(xpp, "ContentProtection")) {
|
||||||
contentProtectionsBuilder.addAdaptationSetProtection(parseContentProtection(xpp));
|
contentProtectionsBuilder.addAdaptationSetProtection(parseContentProtection(xpp));
|
||||||
} else if (isStartTag(xpp, "ContentComponent")) {
|
} else if (ParserUtil.isStartTag(xpp, "ContentComponent")) {
|
||||||
language = checkLanguageConsistency(language, xpp.getAttributeValue(null, "lang"));
|
language = checkLanguageConsistency(language, xpp.getAttributeValue(null, "lang"));
|
||||||
contentType = checkContentTypeConsistency(contentType, parseContentType(xpp));
|
contentType = checkContentTypeConsistency(contentType, parseContentType(xpp));
|
||||||
} else if (isStartTag(xpp, "Representation")) {
|
} else if (ParserUtil.isStartTag(xpp, "Representation")) {
|
||||||
Representation representation = parseRepresentation(xpp, baseUrl, mimeType, codecs, width,
|
Representation representation = parseRepresentation(xpp, baseUrl, mimeType, codecs, width,
|
||||||
height, frameRate, audioChannels, audioSamplingRate, language, segmentBase,
|
height, frameRate, audioChannels, audioSamplingRate, language, segmentBase,
|
||||||
contentProtectionsBuilder);
|
contentProtectionsBuilder);
|
||||||
contentProtectionsBuilder.endRepresentation();
|
contentProtectionsBuilder.endRepresentation();
|
||||||
contentType = checkContentTypeConsistency(contentType, getContentType(representation));
|
contentType = checkContentTypeConsistency(contentType, getContentType(representation));
|
||||||
representations.add(representation);
|
representations.add(representation);
|
||||||
} else if (isStartTag(xpp, "AudioChannelConfiguration")) {
|
} else if (ParserUtil.isStartTag(xpp, "AudioChannelConfiguration")) {
|
||||||
audioChannels = parseAudioChannelConfiguration(xpp);
|
audioChannels = parseAudioChannelConfiguration(xpp);
|
||||||
} else if (isStartTag(xpp, "SegmentBase")) {
|
} else if (ParserUtil.isStartTag(xpp, "SegmentBase")) {
|
||||||
segmentBase = parseSegmentBase(xpp, baseUrl, (SingleSegmentBase) segmentBase);
|
segmentBase = parseSegmentBase(xpp, baseUrl, (SingleSegmentBase) segmentBase);
|
||||||
} else if (isStartTag(xpp, "SegmentList")) {
|
} else if (ParserUtil.isStartTag(xpp, "SegmentList")) {
|
||||||
segmentBase = parseSegmentList(xpp, baseUrl, (SegmentList) segmentBase);
|
segmentBase = parseSegmentList(xpp, baseUrl, (SegmentList) segmentBase);
|
||||||
} else if (isStartTag(xpp, "SegmentTemplate")) {
|
} else if (ParserUtil.isStartTag(xpp, "SegmentTemplate")) {
|
||||||
segmentBase = parseSegmentTemplate(xpp, baseUrl, (SegmentTemplate) segmentBase);
|
segmentBase = parseSegmentTemplate(xpp, baseUrl, (SegmentTemplate) segmentBase);
|
||||||
} else if (isStartTag(xpp)) {
|
} else if (ParserUtil.isStartTag(xpp)) {
|
||||||
parseAdaptationSetChild(xpp);
|
parseAdaptationSetChild(xpp);
|
||||||
}
|
}
|
||||||
} while (!isEndTag(xpp, "AdaptationSet"));
|
} while (!ParserUtil.isEndTag(xpp, "AdaptationSet"));
|
||||||
|
|
||||||
return buildAdaptationSet(id, contentType, representations, contentProtectionsBuilder.build());
|
return buildAdaptationSet(id, contentType, representations, contentProtectionsBuilder.build());
|
||||||
}
|
}
|
||||||
@ -316,14 +317,14 @@ public class MediaPresentationDescriptionParser extends DefaultHandler
|
|||||||
do {
|
do {
|
||||||
xpp.next();
|
xpp.next();
|
||||||
// The cenc:pssh element is defined in 23001-7:2015
|
// The cenc:pssh element is defined in 23001-7:2015
|
||||||
if (isStartTag(xpp, "cenc:pssh") && xpp.next() == XmlPullParser.TEXT) {
|
if (ParserUtil.isStartTag(xpp, "cenc:pssh") && xpp.next() == XmlPullParser.TEXT) {
|
||||||
psshAtom = Base64.decode(xpp.getText(), Base64.DEFAULT);
|
psshAtom = Base64.decode(xpp.getText(), Base64.DEFAULT);
|
||||||
uuid = PsshAtomUtil.parseUuid(psshAtom);
|
uuid = PsshAtomUtil.parseUuid(psshAtom);
|
||||||
if (uuid == null) {
|
if (uuid == null) {
|
||||||
throw new ParserException("Invalid pssh atom in cenc:pssh element");
|
throw new ParserException("Invalid pssh atom in cenc:pssh element");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} while (!isEndTag(xpp, "ContentProtection"));
|
} while (!ParserUtil.isEndTag(xpp, "ContentProtection"));
|
||||||
|
|
||||||
return buildContentProtection(schemeIdUri, uuid, psshAtom);
|
return buildContentProtection(schemeIdUri, uuid, psshAtom);
|
||||||
}
|
}
|
||||||
@ -367,23 +368,23 @@ public class MediaPresentationDescriptionParser extends DefaultHandler
|
|||||||
boolean seenFirstBaseUrl = false;
|
boolean seenFirstBaseUrl = false;
|
||||||
do {
|
do {
|
||||||
xpp.next();
|
xpp.next();
|
||||||
if (isStartTag(xpp, "BaseURL")) {
|
if (ParserUtil.isStartTag(xpp, "BaseURL")) {
|
||||||
if (!seenFirstBaseUrl) {
|
if (!seenFirstBaseUrl) {
|
||||||
baseUrl = parseBaseUrl(xpp, baseUrl);
|
baseUrl = parseBaseUrl(xpp, baseUrl);
|
||||||
seenFirstBaseUrl = true;
|
seenFirstBaseUrl = true;
|
||||||
}
|
}
|
||||||
} else if (isStartTag(xpp, "AudioChannelConfiguration")) {
|
} else if (ParserUtil.isStartTag(xpp, "AudioChannelConfiguration")) {
|
||||||
audioChannels = parseAudioChannelConfiguration(xpp);
|
audioChannels = parseAudioChannelConfiguration(xpp);
|
||||||
} else if (isStartTag(xpp, "SegmentBase")) {
|
} else if (ParserUtil.isStartTag(xpp, "SegmentBase")) {
|
||||||
segmentBase = parseSegmentBase(xpp, baseUrl, (SingleSegmentBase) segmentBase);
|
segmentBase = parseSegmentBase(xpp, baseUrl, (SingleSegmentBase) segmentBase);
|
||||||
} else if (isStartTag(xpp, "SegmentList")) {
|
} else if (ParserUtil.isStartTag(xpp, "SegmentList")) {
|
||||||
segmentBase = parseSegmentList(xpp, baseUrl, (SegmentList) segmentBase);
|
segmentBase = parseSegmentList(xpp, baseUrl, (SegmentList) segmentBase);
|
||||||
} else if (isStartTag(xpp, "SegmentTemplate")) {
|
} else if (ParserUtil.isStartTag(xpp, "SegmentTemplate")) {
|
||||||
segmentBase = parseSegmentTemplate(xpp, baseUrl, (SegmentTemplate) segmentBase);
|
segmentBase = parseSegmentTemplate(xpp, baseUrl, (SegmentTemplate) segmentBase);
|
||||||
} else if (isStartTag(xpp, "ContentProtection")) {
|
} else if (ParserUtil.isStartTag(xpp, "ContentProtection")) {
|
||||||
contentProtectionsBuilder.addRepresentationProtection(parseContentProtection(xpp));
|
contentProtectionsBuilder.addRepresentationProtection(parseContentProtection(xpp));
|
||||||
}
|
}
|
||||||
} while (!isEndTag(xpp, "Representation"));
|
} while (!ParserUtil.isEndTag(xpp, "Representation"));
|
||||||
|
|
||||||
Format format = buildFormat(id, mimeType, width, height, frameRate, audioChannels,
|
Format format = buildFormat(id, mimeType, width, height, frameRate, audioChannels,
|
||||||
audioSamplingRate, bandwidth, language, codecs);
|
audioSamplingRate, bandwidth, language, codecs);
|
||||||
@ -423,10 +424,10 @@ public class MediaPresentationDescriptionParser extends DefaultHandler
|
|||||||
RangedUri initialization = parent != null ? parent.initialization : null;
|
RangedUri initialization = parent != null ? parent.initialization : null;
|
||||||
do {
|
do {
|
||||||
xpp.next();
|
xpp.next();
|
||||||
if (isStartTag(xpp, "Initialization")) {
|
if (ParserUtil.isStartTag(xpp, "Initialization")) {
|
||||||
initialization = parseInitialization(xpp, baseUrl);
|
initialization = parseInitialization(xpp, baseUrl);
|
||||||
}
|
}
|
||||||
} while (!isEndTag(xpp, "SegmentBase"));
|
} while (!ParserUtil.isEndTag(xpp, "SegmentBase"));
|
||||||
|
|
||||||
return buildSingleSegmentBase(initialization, timescale, presentationTimeOffset, baseUrl,
|
return buildSingleSegmentBase(initialization, timescale, presentationTimeOffset, baseUrl,
|
||||||
indexStart, indexLength);
|
indexStart, indexLength);
|
||||||
@ -453,17 +454,17 @@ public class MediaPresentationDescriptionParser extends DefaultHandler
|
|||||||
|
|
||||||
do {
|
do {
|
||||||
xpp.next();
|
xpp.next();
|
||||||
if (isStartTag(xpp, "Initialization")) {
|
if (ParserUtil.isStartTag(xpp, "Initialization")) {
|
||||||
initialization = parseInitialization(xpp, baseUrl);
|
initialization = parseInitialization(xpp, baseUrl);
|
||||||
} else if (isStartTag(xpp, "SegmentTimeline")) {
|
} else if (ParserUtil.isStartTag(xpp, "SegmentTimeline")) {
|
||||||
timeline = parseSegmentTimeline(xpp);
|
timeline = parseSegmentTimeline(xpp);
|
||||||
} else if (isStartTag(xpp, "SegmentURL")) {
|
} else if (ParserUtil.isStartTag(xpp, "SegmentURL")) {
|
||||||
if (segments == null) {
|
if (segments == null) {
|
||||||
segments = new ArrayList<>();
|
segments = new ArrayList<>();
|
||||||
}
|
}
|
||||||
segments.add(parseSegmentUrl(xpp, baseUrl));
|
segments.add(parseSegmentUrl(xpp, baseUrl));
|
||||||
}
|
}
|
||||||
} while (!isEndTag(xpp, "SegmentList"));
|
} while (!ParserUtil.isEndTag(xpp, "SegmentList"));
|
||||||
|
|
||||||
if (parent != null) {
|
if (parent != null) {
|
||||||
initialization = initialization != null ? initialization : parent.initialization;
|
initialization = initialization != null ? initialization : parent.initialization;
|
||||||
@ -500,12 +501,12 @@ public class MediaPresentationDescriptionParser extends DefaultHandler
|
|||||||
|
|
||||||
do {
|
do {
|
||||||
xpp.next();
|
xpp.next();
|
||||||
if (isStartTag(xpp, "Initialization")) {
|
if (ParserUtil.isStartTag(xpp, "Initialization")) {
|
||||||
initialization = parseInitialization(xpp, baseUrl);
|
initialization = parseInitialization(xpp, baseUrl);
|
||||||
} else if (isStartTag(xpp, "SegmentTimeline")) {
|
} else if (ParserUtil.isStartTag(xpp, "SegmentTimeline")) {
|
||||||
timeline = parseSegmentTimeline(xpp);
|
timeline = parseSegmentTimeline(xpp);
|
||||||
}
|
}
|
||||||
} while (!isEndTag(xpp, "SegmentTemplate"));
|
} while (!ParserUtil.isEndTag(xpp, "SegmentTemplate"));
|
||||||
|
|
||||||
if (parent != null) {
|
if (parent != null) {
|
||||||
initialization = initialization != null ? initialization : parent.initialization;
|
initialization = initialization != null ? initialization : parent.initialization;
|
||||||
@ -530,7 +531,7 @@ public class MediaPresentationDescriptionParser extends DefaultHandler
|
|||||||
long elapsedTime = 0;
|
long elapsedTime = 0;
|
||||||
do {
|
do {
|
||||||
xpp.next();
|
xpp.next();
|
||||||
if (isStartTag(xpp, "S")) {
|
if (ParserUtil.isStartTag(xpp, "S")) {
|
||||||
elapsedTime = parseLong(xpp, "t", elapsedTime);
|
elapsedTime = parseLong(xpp, "t", elapsedTime);
|
||||||
long duration = parseLong(xpp, "d");
|
long duration = parseLong(xpp, "d");
|
||||||
int count = 1 + parseInt(xpp, "r", 0);
|
int count = 1 + parseInt(xpp, "r", 0);
|
||||||
@ -539,7 +540,7 @@ public class MediaPresentationDescriptionParser extends DefaultHandler
|
|||||||
elapsedTime += duration;
|
elapsedTime += duration;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} while (!isEndTag(xpp, "SegmentTimeline"));
|
} while (!ParserUtil.isEndTag(xpp, "SegmentTimeline"));
|
||||||
return segmentTimeline;
|
return segmentTimeline;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -592,7 +593,7 @@ public class MediaPresentationDescriptionParser extends DefaultHandler
|
|||||||
int audioChannels = parseInt(xpp, "value");
|
int audioChannels = parseInt(xpp, "value");
|
||||||
do {
|
do {
|
||||||
xpp.next();
|
xpp.next();
|
||||||
} while (!isEndTag(xpp, "AudioChannelConfiguration"));
|
} while (!ParserUtil.isEndTag(xpp, "AudioChannelConfiguration"));
|
||||||
return audioChannels;
|
return audioChannels;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -641,19 +642,6 @@ public class MediaPresentationDescriptionParser extends DefaultHandler
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
protected static boolean isEndTag(XmlPullParser xpp, String name) throws XmlPullParserException {
|
|
||||||
return xpp.getEventType() == XmlPullParser.END_TAG && name.equals(xpp.getName());
|
|
||||||
}
|
|
||||||
|
|
||||||
protected static boolean isStartTag(XmlPullParser xpp, String name)
|
|
||||||
throws XmlPullParserException {
|
|
||||||
return xpp.getEventType() == XmlPullParser.START_TAG && name.equals(xpp.getName());
|
|
||||||
}
|
|
||||||
|
|
||||||
protected static boolean isStartTag(XmlPullParser xpp) throws XmlPullParserException {
|
|
||||||
return xpp.getEventType() == XmlPullParser.START_TAG;
|
|
||||||
}
|
|
||||||
|
|
||||||
protected static float parseFrameRate(XmlPullParser xpp, float defaultValue) {
|
protected static float parseFrameRate(XmlPullParser xpp, float defaultValue) {
|
||||||
float frameRate = defaultValue;
|
float frameRate = defaultValue;
|
||||||
String frameRateAttribute = xpp.getAttributeValue(null, "frameRate");
|
String frameRateAttribute = xpp.getAttributeValue(null, "frameRate");
|
||||||
|
@ -15,7 +15,16 @@
|
|||||||
*/
|
*/
|
||||||
package com.google.android.exoplayer.text.ttml;
|
package com.google.android.exoplayer.text.ttml;
|
||||||
|
|
||||||
|
import android.text.Spannable;
|
||||||
import android.text.SpannableStringBuilder;
|
import android.text.SpannableStringBuilder;
|
||||||
|
import android.text.Spanned;
|
||||||
|
import android.text.style.AlignmentSpan;
|
||||||
|
import android.text.style.BackgroundColorSpan;
|
||||||
|
import android.text.style.ForegroundColorSpan;
|
||||||
|
import android.text.style.StrikethroughSpan;
|
||||||
|
import android.text.style.StyleSpan;
|
||||||
|
import android.text.style.TypefaceSpan;
|
||||||
|
import android.text.style.UnderlineSpan;
|
||||||
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.Iterator;
|
import java.util.Iterator;
|
||||||
@ -28,7 +37,6 @@ import java.util.TreeSet;
|
|||||||
/* package */ final class TtmlNode {
|
/* package */ final class TtmlNode {
|
||||||
|
|
||||||
public static final long UNDEFINED_TIME = -1;
|
public static final long UNDEFINED_TIME = -1;
|
||||||
|
|
||||||
public static final String TAG_TT = "tt";
|
public static final String TAG_TT = "tt";
|
||||||
public static final String TAG_HEAD = "head";
|
public static final String TAG_HEAD = "head";
|
||||||
public static final String TAG_BODY = "body";
|
public static final String TAG_BODY = "body";
|
||||||
@ -45,25 +53,51 @@ import java.util.TreeSet;
|
|||||||
public static final String TAG_SMPTE_DATA = "smpte:data";
|
public static final String TAG_SMPTE_DATA = "smpte:data";
|
||||||
public static final String TAG_SMPTE_INFORMATION = "smpte:information";
|
public static final String TAG_SMPTE_INFORMATION = "smpte:information";
|
||||||
|
|
||||||
|
public static final String ATTR_ID = "id";
|
||||||
|
public static final String ATTR_TTS_BACKGROUND_COLOR = "backgroundColor";
|
||||||
|
public static final String ATTR_TTS_FONT_STYLE = "fontStyle";
|
||||||
|
public static final String ATTR_TTS_FONT_SIZE = "fontSize";
|
||||||
|
public static final String ATTR_TTS_FONT_FAMILY = "fontFamily";
|
||||||
|
public static final String ATTR_TTS_FONT_WEIGHT = "fontWeight";
|
||||||
|
public static final String ATTR_TTS_COLOR = "color";
|
||||||
|
public static final String ATTR_TTS_TEXT_DECORATION = "textDecoration";
|
||||||
|
public static final String ATTR_TTS_TEXT_ALIGN = "textAlign";
|
||||||
|
|
||||||
|
public static final String LINETHROUGH = "linethrough";
|
||||||
|
public static final String NO_LINETHROUGH = "nolinethrough";
|
||||||
|
public static final String UNDERLINE = "underline";
|
||||||
|
public static final String NO_UNDERLINE = "nounderline";
|
||||||
|
public static final String ITALIC = "italic";
|
||||||
|
public static final String BOLD = "bold";
|
||||||
|
|
||||||
|
public static final String LEFT = "left";
|
||||||
|
public static final String CENTER = "center";
|
||||||
|
public static final String RIGHT = "right";
|
||||||
|
public static final String START = "start";
|
||||||
|
public static final String END = "end";
|
||||||
|
|
||||||
public final String tag;
|
public final String tag;
|
||||||
public final String text;
|
public final String text;
|
||||||
public final boolean isTextNode;
|
public final boolean isTextNode;
|
||||||
public final long startTimeUs;
|
public final long startTimeUs;
|
||||||
public final long endTimeUs;
|
public final long endTimeUs;
|
||||||
|
public final TtmlStyle style;
|
||||||
|
|
||||||
private List<TtmlNode> children;
|
private List<TtmlNode> children;
|
||||||
|
|
||||||
public static TtmlNode buildTextNode(String text) {
|
public static TtmlNode buildTextNode(String text, TtmlStyle style) {
|
||||||
return new TtmlNode(null, applyTextElementSpacePolicy(text), UNDEFINED_TIME, UNDEFINED_TIME);
|
return new TtmlNode(null, applyTextElementSpacePolicy(text), UNDEFINED_TIME,
|
||||||
|
UNDEFINED_TIME, style);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static TtmlNode buildNode(String tag, long startTimeUs, long endTimeUs) {
|
public static TtmlNode buildNode(String tag, long startTimeUs, long endTimeUs, TtmlStyle style) {
|
||||||
return new TtmlNode(tag, null, startTimeUs, endTimeUs);
|
return new TtmlNode(tag, null, startTimeUs, endTimeUs, style);
|
||||||
}
|
}
|
||||||
|
|
||||||
private TtmlNode(String tag, String text, long startTimeUs, long endTimeUs) {
|
private TtmlNode(String tag, String text, long startTimeUs, long endTimeUs, TtmlStyle style) {
|
||||||
this.tag = tag;
|
this.tag = tag;
|
||||||
this.text = text;
|
this.text = text;
|
||||||
|
this.style = style;
|
||||||
this.isTextNode = text != null;
|
this.isTextNode = text != null;
|
||||||
this.startTimeUs = startTimeUs;
|
this.startTimeUs = startTimeUs;
|
||||||
this.endTimeUs = endTimeUs;
|
this.endTimeUs = endTimeUs;
|
||||||
@ -168,15 +202,18 @@ import java.util.TreeSet;
|
|||||||
// 4. Trim a trailing newline, if there is one.
|
// 4. Trim a trailing newline, if there is one.
|
||||||
if (builderLength > 0 && builder.charAt(builderLength - 1) == '\n') {
|
if (builderLength > 0 && builder.charAt(builderLength - 1) == '\n') {
|
||||||
builder.delete(builderLength - 1, builderLength);
|
builder.delete(builderLength - 1, builderLength);
|
||||||
builderLength--;
|
/*builderLength--;*/
|
||||||
}
|
}
|
||||||
return builder.subSequence(0, builderLength);
|
|
||||||
|
return builder;
|
||||||
}
|
}
|
||||||
|
|
||||||
private SpannableStringBuilder getText(long timeUs, SpannableStringBuilder builder,
|
private SpannableStringBuilder getText(long timeUs, SpannableStringBuilder builder,
|
||||||
boolean descendsPNode) {
|
boolean descendsPNode) {
|
||||||
if (isTextNode && descendsPNode) {
|
if (isTextNode && descendsPNode) {
|
||||||
|
int start = builder.length();
|
||||||
builder.append(text);
|
builder.append(text);
|
||||||
|
applyStylesToSpan(builder, start, builder.length(), style);
|
||||||
} else if (TAG_BR.equals(tag) && descendsPNode) {
|
} else if (TAG_BR.equals(tag) && descendsPNode) {
|
||||||
builder.append('\n');
|
builder.append('\n');
|
||||||
} else if (TAG_METADATA.equals(tag)) {
|
} else if (TAG_METADATA.equals(tag)) {
|
||||||
@ -193,6 +230,37 @@ import java.util.TreeSet;
|
|||||||
return builder;
|
return builder;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static void applyStylesToSpan(SpannableStringBuilder builder,
|
||||||
|
int start, int end, TtmlStyle style) {
|
||||||
|
|
||||||
|
if (style.getStyle() != TtmlStyle.UNSPECIFIED) {
|
||||||
|
builder.setSpan(new StyleSpan(style.getStyle()), start, end,
|
||||||
|
Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
|
||||||
|
}
|
||||||
|
if (style.isLinethrough()) {
|
||||||
|
builder.setSpan(new StrikethroughSpan(), start, end, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
|
||||||
|
}
|
||||||
|
if (style.isUnderline()) {
|
||||||
|
builder.setSpan(new UnderlineSpan(), start, end, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
|
||||||
|
}
|
||||||
|
if (style.hasColorSpecified()) {
|
||||||
|
builder.setSpan(new ForegroundColorSpan(style.getColor()), start, end,
|
||||||
|
Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
|
||||||
|
}
|
||||||
|
if (style.hasBackgroundColorSpecified()) {
|
||||||
|
builder.setSpan(new BackgroundColorSpan(style.getBackgroundColor()), start, end,
|
||||||
|
Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
|
||||||
|
}
|
||||||
|
if (style.getFontFamily() != null) {
|
||||||
|
builder.setSpan(new TypefaceSpan(style.getFontFamily()), start, end,
|
||||||
|
Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
|
||||||
|
}
|
||||||
|
if (style.getTextAlign() != null) {
|
||||||
|
builder.setSpan(new AlignmentSpan.Standard(style.getTextAlign()), start, end,
|
||||||
|
Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Invoked when the end of a paragraph is encountered. Adds a newline if there are one or more
|
* Invoked when the end of a paragraph is encountered. Adds a newline if there are one or more
|
||||||
* non-space characters since the previous newline.
|
* non-space characters since the previous newline.
|
||||||
|
@ -20,7 +20,10 @@ import com.google.android.exoplayer.ParserException;
|
|||||||
import com.google.android.exoplayer.text.Subtitle;
|
import com.google.android.exoplayer.text.Subtitle;
|
||||||
import com.google.android.exoplayer.text.SubtitleParser;
|
import com.google.android.exoplayer.text.SubtitleParser;
|
||||||
import com.google.android.exoplayer.util.MimeTypes;
|
import com.google.android.exoplayer.util.MimeTypes;
|
||||||
|
import com.google.android.exoplayer.util.ParserUtil;
|
||||||
|
|
||||||
|
import android.graphics.Color;
|
||||||
|
import android.text.Layout;
|
||||||
import android.util.Log;
|
import android.util.Log;
|
||||||
|
|
||||||
import org.xmlpull.v1.XmlPullParser;
|
import org.xmlpull.v1.XmlPullParser;
|
||||||
@ -29,7 +32,9 @@ import org.xmlpull.v1.XmlPullParserFactory;
|
|||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.io.InputStream;
|
import java.io.InputStream;
|
||||||
|
import java.util.HashMap;
|
||||||
import java.util.LinkedList;
|
import java.util.LinkedList;
|
||||||
|
import java.util.Map;
|
||||||
import java.util.regex.Matcher;
|
import java.util.regex.Matcher;
|
||||||
import java.util.regex.Pattern;
|
import java.util.regex.Pattern;
|
||||||
|
|
||||||
@ -61,6 +66,7 @@ public final class TtmlParser implements SubtitleParser {
|
|||||||
private static final String ATTR_BEGIN = "begin";
|
private static final String ATTR_BEGIN = "begin";
|
||||||
private static final String ATTR_DURATION = "dur";
|
private static final String ATTR_DURATION = "dur";
|
||||||
private static final String ATTR_END = "end";
|
private static final String ATTR_END = "end";
|
||||||
|
private static final String ATTR_STYLE = "style";
|
||||||
|
|
||||||
private static final Pattern CLOCK_TIME =
|
private static final Pattern CLOCK_TIME =
|
||||||
Pattern.compile("^([0-9][0-9]+):([0-9][0-9]):([0-9][0-9])"
|
Pattern.compile("^([0-9][0-9]+):([0-9][0-9]):([0-9][0-9])"
|
||||||
@ -102,6 +108,7 @@ public final class TtmlParser implements SubtitleParser {
|
|||||||
public Subtitle parse(InputStream inputStream) throws IOException {
|
public Subtitle parse(InputStream inputStream) throws IOException {
|
||||||
try {
|
try {
|
||||||
XmlPullParser xmlParser = xmlParserFactory.newPullParser();
|
XmlPullParser xmlParser = xmlParserFactory.newPullParser();
|
||||||
|
Map<String, TtmlStyle> globalStyles = new HashMap<>();
|
||||||
xmlParser.setInput(inputStream, null);
|
xmlParser.setInput(inputStream, null);
|
||||||
TtmlSubtitle ttmlSubtitle = null;
|
TtmlSubtitle ttmlSubtitle = null;
|
||||||
LinkedList<TtmlNode> nodeStack = new LinkedList<>();
|
LinkedList<TtmlNode> nodeStack = new LinkedList<>();
|
||||||
@ -115,9 +122,11 @@ public final class TtmlParser implements SubtitleParser {
|
|||||||
if (!isSupportedTag(name)) {
|
if (!isSupportedTag(name)) {
|
||||||
Log.i(TAG, "Ignoring unsupported tag: " + xmlParser.getName());
|
Log.i(TAG, "Ignoring unsupported tag: " + xmlParser.getName());
|
||||||
unsupportedNodeDepth++;
|
unsupportedNodeDepth++;
|
||||||
|
} else if (TtmlNode.TAG_HEAD.equals(name)) {
|
||||||
|
parseHeader(xmlParser, globalStyles);
|
||||||
} else {
|
} else {
|
||||||
try {
|
try {
|
||||||
TtmlNode node = parseNode(xmlParser, parent);
|
TtmlNode node = parseNode(xmlParser, parent, globalStyles);
|
||||||
nodeStack.addLast(node);
|
nodeStack.addLast(node);
|
||||||
if (parent != null) {
|
if (parent != null) {
|
||||||
parent.addChild(node);
|
parent.addChild(node);
|
||||||
@ -133,7 +142,7 @@ public final class TtmlParser implements SubtitleParser {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else if (eventType == XmlPullParser.TEXT) {
|
} else if (eventType == XmlPullParser.TEXT) {
|
||||||
parent.addChild(TtmlNode.buildTextNode(xmlParser.getText()));
|
parent.addChild(TtmlNode.buildTextNode(xmlParser.getText(), parent.style));
|
||||||
} else if (eventType == XmlPullParser.END_TAG) {
|
} else if (eventType == XmlPullParser.END_TAG) {
|
||||||
if (xmlParser.getName().equals(TtmlNode.TAG_TT)) {
|
if (xmlParser.getName().equals(TtmlNode.TAG_TT)) {
|
||||||
ttmlSubtitle = new TtmlSubtitle(nodeStack.getLast());
|
ttmlSubtitle = new TtmlSubtitle(nodeStack.getLast());
|
||||||
@ -156,19 +165,141 @@ public final class TtmlParser implements SubtitleParser {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private Map<String, TtmlStyle> parseHeader(XmlPullParser xmlParser,
|
||||||
|
Map<String, TtmlStyle> globalStyles)
|
||||||
|
throws IOException, XmlPullParserException {
|
||||||
|
|
||||||
|
do {
|
||||||
|
xmlParser.next();
|
||||||
|
if (ParserUtil.isStartTag(xmlParser, TtmlNode.TAG_STYLE)) {
|
||||||
|
String parentStyleId = xmlParser.getAttributeValue(null, ATTR_STYLE);
|
||||||
|
TtmlStyle style = parseStyleAttributes(xmlParser, new TtmlStyle());
|
||||||
|
if (parentStyleId != null) {
|
||||||
|
String[] ids = parentStyleId.split(" ");
|
||||||
|
for (int i = 0; i < ids.length; i++) {
|
||||||
|
style.chain(globalStyles.get(ids[i]));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (style.getId() != null) {
|
||||||
|
globalStyles.put(style.getId(), style);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} while (!ParserUtil.isEndTag(xmlParser, TtmlNode.TAG_HEAD));
|
||||||
|
return globalStyles;
|
||||||
|
}
|
||||||
|
|
||||||
|
private TtmlStyle parseStyleAttributes(XmlPullParser parser, TtmlStyle style) {
|
||||||
|
int attributeCount = parser.getAttributeCount();
|
||||||
|
for (int i = 0; i < attributeCount; i++) {
|
||||||
|
String attributeName = parser.getAttributeName(i);
|
||||||
|
String attributeValue = parser.getAttributeValue(i);
|
||||||
|
// TODO: check if it is safe to remove the namespace prefix
|
||||||
|
switch (ParserUtil.removeNamespacePrefix(attributeName)) {
|
||||||
|
case TtmlNode.ATTR_ID:
|
||||||
|
if (TtmlNode.TAG_STYLE.equals(parser.getName())) {
|
||||||
|
style = createIfNull(style).setId(attributeValue);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case TtmlNode.ATTR_TTS_BACKGROUND_COLOR:
|
||||||
|
style = createIfNull(style);
|
||||||
|
try {
|
||||||
|
style.setBackgroundColor(Color.parseColor(attributeValue));
|
||||||
|
} catch (IllegalArgumentException e) {
|
||||||
|
Log.w(TAG, "failed parsing background value: '" + attributeValue + "'");
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case TtmlNode.ATTR_TTS_COLOR:
|
||||||
|
style = createIfNull(style);
|
||||||
|
try {
|
||||||
|
style.setColor(Color.parseColor(attributeValue));
|
||||||
|
} catch (IllegalArgumentException e) {
|
||||||
|
Log.w(TAG, "failed parsing color value: '" + attributeValue + "'");
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case TtmlNode.ATTR_TTS_FONT_FAMILY:
|
||||||
|
style = createIfNull(style).setFontFamily(attributeValue);
|
||||||
|
break;
|
||||||
|
case TtmlNode.ATTR_TTS_FONT_SIZE:
|
||||||
|
// TODO: handle size
|
||||||
|
break;
|
||||||
|
case TtmlNode.ATTR_TTS_FONT_WEIGHT:
|
||||||
|
style = createIfNull(style).setBold(
|
||||||
|
TtmlNode.BOLD.equals(attributeValue.toLowerCase()));
|
||||||
|
break;
|
||||||
|
case TtmlNode.ATTR_TTS_FONT_STYLE:
|
||||||
|
style = createIfNull(style).setItalic(
|
||||||
|
TtmlNode.ITALIC.equals(attributeValue.toLowerCase()));
|
||||||
|
break;
|
||||||
|
case TtmlNode.ATTR_TTS_TEXT_ALIGN:
|
||||||
|
switch (attributeValue.toLowerCase()) {
|
||||||
|
case TtmlNode.LEFT:
|
||||||
|
style = createIfNull(style).setTextAlign(Layout.Alignment.ALIGN_NORMAL);
|
||||||
|
break;
|
||||||
|
case TtmlNode.START:
|
||||||
|
style = createIfNull(style).setTextAlign(Layout.Alignment.ALIGN_NORMAL);
|
||||||
|
break;
|
||||||
|
case TtmlNode.RIGHT:
|
||||||
|
style = createIfNull(style).setTextAlign(Layout.Alignment.ALIGN_OPPOSITE);
|
||||||
|
break;
|
||||||
|
case TtmlNode.END:
|
||||||
|
style = createIfNull(style).setTextAlign(Layout.Alignment.ALIGN_OPPOSITE);
|
||||||
|
break;
|
||||||
|
case TtmlNode.CENTER:
|
||||||
|
style = createIfNull(style).setTextAlign(Layout.Alignment.ALIGN_CENTER);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case TtmlNode.ATTR_TTS_TEXT_DECORATION:
|
||||||
|
switch (attributeValue.toLowerCase()) {
|
||||||
|
case TtmlNode.LINETHROUGH:
|
||||||
|
style = createIfNull(style).setLinethrough(true);
|
||||||
|
break;
|
||||||
|
case TtmlNode.NO_LINETHROUGH:
|
||||||
|
style = createIfNull(style).setLinethrough(false);
|
||||||
|
break;
|
||||||
|
case TtmlNode.UNDERLINE:
|
||||||
|
style = createIfNull(style).setUnderline(true);
|
||||||
|
break;
|
||||||
|
case TtmlNode.NO_UNDERLINE:
|
||||||
|
style = createIfNull(style).setUnderline(false);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
// ignore
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return style;
|
||||||
|
}
|
||||||
|
|
||||||
|
private TtmlStyle createIfNull(TtmlStyle style) {
|
||||||
|
return style == null ? new TtmlStyle() : style;
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean canParse(String mimeType) {
|
public boolean canParse(String mimeType) {
|
||||||
return MimeTypes.APPLICATION_TTML.equals(mimeType);
|
return MimeTypes.APPLICATION_TTML.equals(mimeType);
|
||||||
}
|
}
|
||||||
|
|
||||||
private TtmlNode parseNode(XmlPullParser parser, TtmlNode parent) throws ParserException {
|
private TtmlNode parseNode(XmlPullParser parser, TtmlNode parent,
|
||||||
|
Map<String, TtmlStyle> globalStyles) throws ParserException {
|
||||||
long duration = 0;
|
long duration = 0;
|
||||||
long startTime = TtmlNode.UNDEFINED_TIME;
|
long startTime = TtmlNode.UNDEFINED_TIME;
|
||||||
long endTime = TtmlNode.UNDEFINED_TIME;
|
long endTime = TtmlNode.UNDEFINED_TIME;
|
||||||
int attributeCount = parser.getAttributeCount();
|
int attributeCount = parser.getAttributeCount();
|
||||||
|
TtmlStyle style = parseStyleAttributes(parser, null);
|
||||||
|
boolean hasInlineStyles = style != null;
|
||||||
|
if (parent != null && parent.style != null) {
|
||||||
|
if (hasInlineStyles) {
|
||||||
|
style.inherit(parent.style);
|
||||||
|
} else {
|
||||||
|
style = parent.style.getInheritableStyle();
|
||||||
|
}
|
||||||
|
}
|
||||||
for (int i = 0; i < attributeCount; i++) {
|
for (int i = 0; i < attributeCount; i++) {
|
||||||
// TODO: check if it's safe to ignore the namespace of attributes as follows.
|
// TODO: check if it is safe to remove the namespace prefix
|
||||||
String attr = parser.getAttributeName(i).replaceFirst("^.*:", "");
|
String attr = ParserUtil.removeNamespacePrefix(parser.getAttributeName(i));
|
||||||
String value = parser.getAttributeValue(i);
|
String value = parser.getAttributeValue(i);
|
||||||
if (attr.equals(ATTR_BEGIN)) {
|
if (attr.equals(ATTR_BEGIN)) {
|
||||||
startTime = parseTimeExpression(value,
|
startTime = parseTimeExpression(value,
|
||||||
@ -179,6 +310,34 @@ public final class TtmlParser implements SubtitleParser {
|
|||||||
} else if (attr.equals(ATTR_DURATION)) {
|
} else if (attr.equals(ATTR_DURATION)) {
|
||||||
duration = parseTimeExpression(value,
|
duration = parseTimeExpression(value,
|
||||||
DEFAULT_FRAMERATE, DEFAULT_SUBFRAMERATE, DEFAULT_TICKRATE);
|
DEFAULT_FRAMERATE, DEFAULT_SUBFRAMERATE, DEFAULT_TICKRATE);
|
||||||
|
} else if (attr.equals(ATTR_STYLE)) {
|
||||||
|
// IDREFS: potentially multiple space delimited ids
|
||||||
|
String[] ids = value.split(" ");
|
||||||
|
if (style == null) {
|
||||||
|
// use global style without overriding
|
||||||
|
if (ids.length == 1) {
|
||||||
|
style = globalStyles.get(value);
|
||||||
|
} else if (ids.length > 1){
|
||||||
|
style = new TtmlStyle();
|
||||||
|
for (int j = 0; j < ids.length; j++) {
|
||||||
|
style.chain(globalStyles.get(ids[j]));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else if (hasInlineStyles) {
|
||||||
|
// local attributes inherits from global style
|
||||||
|
for (int j = 0; j < ids.length; j++) {
|
||||||
|
style.chain(globalStyles.get(ids[j]));
|
||||||
|
}
|
||||||
|
} else if (ids.length > 1 || (ids.length == 1 && style != globalStyles.get(ids[0]))) {
|
||||||
|
// merge global style and parent styles
|
||||||
|
TtmlStyle inheritedStyles = style;
|
||||||
|
style = new TtmlStyle();
|
||||||
|
for (int j = 0; j < ids.length; j++) {
|
||||||
|
style.chain(globalStyles.get(ids[j]));
|
||||||
|
}
|
||||||
|
style.inherit(inheritedStyles);
|
||||||
|
}
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
// Do nothing.
|
// Do nothing.
|
||||||
}
|
}
|
||||||
@ -200,7 +359,7 @@ public final class TtmlParser implements SubtitleParser {
|
|||||||
endTime = parent.endTimeUs;
|
endTime = parent.endTimeUs;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return TtmlNode.buildNode(parser.getName(), startTime, endTime);
|
return TtmlNode.buildNode(parser.getName(), startTime, endTime, style);
|
||||||
}
|
}
|
||||||
|
|
||||||
private static boolean isSupportedTag(String tag) {
|
private static boolean isSupportedTag(String tag) {
|
||||||
|
@ -0,0 +1,224 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (C) 2015 The Android Open Source Project
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
package com.google.android.exoplayer.text.ttml;
|
||||||
|
|
||||||
|
import com.google.android.exoplayer.util.Assertions;
|
||||||
|
|
||||||
|
import android.graphics.Typeface;
|
||||||
|
import android.text.Layout;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Style object of a <code>TtmlNode</code>
|
||||||
|
*/
|
||||||
|
public final class TtmlStyle {
|
||||||
|
|
||||||
|
public static final short UNSPECIFIED = -1;
|
||||||
|
|
||||||
|
public static final short STYLE_NORMAL = Typeface.NORMAL;
|
||||||
|
public static final short STYLE_BOLD = Typeface.BOLD;
|
||||||
|
public static final short STYLE_ITALIC = Typeface.ITALIC;
|
||||||
|
public static final short STYLE_BOLD_ITALIC = Typeface.BOLD_ITALIC;
|
||||||
|
|
||||||
|
private static final short OFF = 0;
|
||||||
|
private static final short ON = 1;
|
||||||
|
|
||||||
|
private String fontFamily;
|
||||||
|
private int color;
|
||||||
|
private boolean colorSpecified;
|
||||||
|
private int backgroundColor;
|
||||||
|
private boolean backgroundColorSpecified;
|
||||||
|
private short linethrough = UNSPECIFIED;
|
||||||
|
private short underline = UNSPECIFIED;
|
||||||
|
private short bold = UNSPECIFIED;
|
||||||
|
private short italic = UNSPECIFIED;
|
||||||
|
private String id;
|
||||||
|
private TtmlStyle inheritableStyle;
|
||||||
|
private Layout.Alignment textAlign;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the style or <code>UNSPECIFIED</code> when no style information is given.
|
||||||
|
*
|
||||||
|
* @return UNSPECIFIED, STYLE_NORMAL, STYLE_BOLD, STYLE_BOLD or STYLE_BOLD_ITALIC
|
||||||
|
*/
|
||||||
|
public short getStyle() {
|
||||||
|
if (bold == UNSPECIFIED && italic == UNSPECIFIED) {
|
||||||
|
return UNSPECIFIED;
|
||||||
|
}
|
||||||
|
|
||||||
|
short style = STYLE_NORMAL;
|
||||||
|
if (bold != UNSPECIFIED) {
|
||||||
|
style += bold;
|
||||||
|
}
|
||||||
|
if (italic != UNSPECIFIED){
|
||||||
|
style += italic;
|
||||||
|
}
|
||||||
|
return style;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isLinethrough() {
|
||||||
|
return linethrough == ON;
|
||||||
|
}
|
||||||
|
|
||||||
|
public TtmlStyle setLinethrough(boolean linethrough) {
|
||||||
|
Assertions.checkState(inheritableStyle == null);
|
||||||
|
this.linethrough = linethrough ? ON : OFF;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isUnderline() {
|
||||||
|
return underline == ON;
|
||||||
|
}
|
||||||
|
|
||||||
|
public TtmlStyle setUnderline(boolean underline) {
|
||||||
|
Assertions.checkState(inheritableStyle == null);
|
||||||
|
this.underline = underline ? ON : OFF;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getFontFamily() {
|
||||||
|
return fontFamily;
|
||||||
|
}
|
||||||
|
|
||||||
|
public TtmlStyle setFontFamily(String fontFamily) {
|
||||||
|
Assertions.checkState(inheritableStyle == null);
|
||||||
|
this.fontFamily = fontFamily;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getColor() {
|
||||||
|
return color;
|
||||||
|
}
|
||||||
|
|
||||||
|
public TtmlStyle setColor(int color) {
|
||||||
|
Assertions.checkState(inheritableStyle == null);
|
||||||
|
this.color = color;
|
||||||
|
colorSpecified = true;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean hasColorSpecified() {
|
||||||
|
return colorSpecified;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getBackgroundColor() {
|
||||||
|
return backgroundColor;
|
||||||
|
}
|
||||||
|
|
||||||
|
public TtmlStyle setBackgroundColor(int backgroundColor) {
|
||||||
|
this.backgroundColor = backgroundColor;
|
||||||
|
backgroundColorSpecified = true;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean hasBackgroundColorSpecified() {
|
||||||
|
return backgroundColorSpecified;
|
||||||
|
}
|
||||||
|
|
||||||
|
public TtmlStyle setBold(boolean isBold) {
|
||||||
|
Assertions.checkState(inheritableStyle == null);
|
||||||
|
bold = isBold ? STYLE_BOLD : STYLE_NORMAL;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public TtmlStyle setItalic(boolean isItalic) {
|
||||||
|
Assertions.checkState(inheritableStyle == null);
|
||||||
|
italic = isItalic ? STYLE_ITALIC : STYLE_NORMAL;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public TtmlStyle getInheritableStyle() {
|
||||||
|
if (isFullyInheritable()) {
|
||||||
|
return this;
|
||||||
|
} else if (inheritableStyle == null) {
|
||||||
|
inheritableStyle = new TtmlStyle().inherit(this);
|
||||||
|
}
|
||||||
|
return inheritableStyle;
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean isFullyInheritable() {
|
||||||
|
return !backgroundColorSpecified;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Inherits from an ancestor style. Properties like <i>tts:backgroundColor</i> which
|
||||||
|
* are not inheritable are not inherited as well as properties which are already set locally
|
||||||
|
* are never overridden.
|
||||||
|
*
|
||||||
|
* @param ancestor the ancestor style to inherit from
|
||||||
|
*/
|
||||||
|
public TtmlStyle inherit(TtmlStyle ancestor) {
|
||||||
|
return inherit(ancestor, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Chains this style to referential style. Local properties which are already set
|
||||||
|
* are never overridden.
|
||||||
|
*
|
||||||
|
* @param ancestor the referential style to inherit from
|
||||||
|
*/
|
||||||
|
public TtmlStyle chain(TtmlStyle ancestor) {
|
||||||
|
return inherit(ancestor, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
private TtmlStyle inherit(TtmlStyle ancestor, boolean chaining) {
|
||||||
|
if (ancestor != null) {
|
||||||
|
if (!colorSpecified && ancestor.colorSpecified) {
|
||||||
|
setColor(ancestor.color);
|
||||||
|
}
|
||||||
|
if (bold == UNSPECIFIED) {
|
||||||
|
bold = ancestor.bold;
|
||||||
|
}
|
||||||
|
if (italic == UNSPECIFIED) {
|
||||||
|
italic = ancestor.italic;
|
||||||
|
}
|
||||||
|
if (fontFamily == null) {
|
||||||
|
fontFamily = ancestor.fontFamily;
|
||||||
|
}
|
||||||
|
if (linethrough == UNSPECIFIED) {
|
||||||
|
linethrough = ancestor.linethrough;
|
||||||
|
}
|
||||||
|
if (underline == UNSPECIFIED) {
|
||||||
|
underline = ancestor.underline;
|
||||||
|
}
|
||||||
|
if (textAlign == null) {
|
||||||
|
textAlign = ancestor.textAlign;
|
||||||
|
}
|
||||||
|
// attributes not inherited as of http://www.w3.org/TR/ttml1/
|
||||||
|
if (chaining && !backgroundColorSpecified && ancestor.backgroundColorSpecified) {
|
||||||
|
setBackgroundColor(ancestor.backgroundColor);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public TtmlStyle setId(String id) {
|
||||||
|
this.id = id;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getId() {
|
||||||
|
return id;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Layout.Alignment getTextAlign() {
|
||||||
|
return textAlign;
|
||||||
|
}
|
||||||
|
|
||||||
|
public TtmlStyle setTextAlign(Layout.Alignment textAlign) {
|
||||||
|
this.textAlign = textAlign;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
}
|
@ -56,6 +56,11 @@ public final class TtmlSubtitle implements Subtitle {
|
|||||||
return (eventTimesUs.length == 0 ? -1 : eventTimesUs[eventTimesUs.length - 1]);
|
return (eventTimesUs.length == 0 ? -1 : eventTimesUs[eventTimesUs.length - 1]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* @VisibleForTesting */
|
||||||
|
/* package */ TtmlNode getRoot() {
|
||||||
|
return root;
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public List<Cue> getCues(long timeUs) {
|
public List<Cue> getCues(long timeUs) {
|
||||||
CharSequence cueText = root.getText(timeUs);
|
CharSequence cueText = root.getText(timeUs);
|
||||||
|
@ -0,0 +1,50 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (C) 2014 The Android Open Source Project
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
package com.google.android.exoplayer.util;
|
||||||
|
|
||||||
|
import org.xmlpull.v1.XmlPullParser;
|
||||||
|
import org.xmlpull.v1.XmlPullParserException;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Parser utility functions.
|
||||||
|
*/
|
||||||
|
public final class ParserUtil {
|
||||||
|
|
||||||
|
private ParserUtil() {}
|
||||||
|
|
||||||
|
public static boolean isEndTag(XmlPullParser xpp, String name) throws XmlPullParserException {
|
||||||
|
return xpp.getEventType() == XmlPullParser.END_TAG && name.equals(xpp.getName());
|
||||||
|
}
|
||||||
|
|
||||||
|
public static boolean isStartTag(XmlPullParser xpp, String name)
|
||||||
|
throws XmlPullParserException {
|
||||||
|
return xpp.getEventType() == XmlPullParser.START_TAG && name.equals(xpp.getName());
|
||||||
|
}
|
||||||
|
|
||||||
|
public static boolean isStartTag(XmlPullParser xpp) throws XmlPullParserException {
|
||||||
|
return xpp.getEventType() == XmlPullParser.START_TAG;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Removes the namespace part ('^.*:') of the attributeName.
|
||||||
|
*
|
||||||
|
* @param attributeName the string to remove the namespace prefix from
|
||||||
|
* @return the name of the attribute without the prefix
|
||||||
|
*/
|
||||||
|
public static String removeNamespacePrefix(String attributeName) {
|
||||||
|
return attributeName.replaceFirst("^.*:", "");
|
||||||
|
}
|
||||||
|
}
|
Loading…
x
Reference in New Issue
Block a user