diff --git a/library/src/androidTest/java/com/google/android/exoplayer/text/webvtt/CssParserTest.java b/library/src/androidTest/java/com/google/android/exoplayer/text/webvtt/CssParserTest.java index 367b5f4256..4f3959f23d 100644 --- a/library/src/androidTest/java/com/google/android/exoplayer/text/webvtt/CssParserTest.java +++ b/library/src/androidTest/java/com/google/android/exoplayer/text/webvtt/CssParserTest.java @@ -178,6 +178,36 @@ public final class CssParserTest extends InstrumentationTestCase { assertEquals(CssParser.parseNextToken(input, builder), null); } + public void testStyleScoreSystem() { + WebvttCssStyle style = new WebvttCssStyle(); + // Universal selector. + assertEquals(1, style.getSpecificityScore(null, null, new String[0], null)); + // Class match without tag match. + style.setTargetClasses(new String[] { "class1", "class2"}); + assertEquals(8, style.getSpecificityScore(null, null, + new String[] { "class1", "class2", "class3" }, null)); + // Class and tag match + style.setTargetTagName("b"); + assertEquals(10, style.getSpecificityScore(null, "b", + new String[] { "class1", "class2", "class3" }, null)); + // Class insufficiency. + assertEquals(0, style.getSpecificityScore(null, "b", new String[] { "class1", "class" }, null)); + // Voice, classes and tag match. + style.setTargetVoice("Manuel Cráneo"); + assertEquals(14, style.getSpecificityScore(null, "b", + new String[] { "class1", "class2", "class3" }, "Manuel Cráneo")); + // Voice mismatch. + assertEquals(0, style.getSpecificityScore(null, "b", + new String[] { "class1", "class2", "class3" }, "Manuel Craneo")); + // Id, voice, classes and tag match. + style.setTargetId("id"); + assertEquals(0x40000000 + 14, style.getSpecificityScore("id", "b", + new String[] { "class1", "class2", "class3" }, "Manuel Cráneo")); + // Id mismatch. + assertEquals(0, style.getSpecificityScore("id1", "b", + new String[] { "class1", "class2", "class3" }, null)); + } + // Utility methods. private void assertSkipsToEndOfSkip(String expectedLine, String s) { diff --git a/library/src/main/java/com/google/android/exoplayer/text/webvtt/WebvttCssStyle.java b/library/src/main/java/com/google/android/exoplayer/text/webvtt/WebvttCssStyle.java index be229e1a88..508dc8d16d 100644 --- a/library/src/main/java/com/google/android/exoplayer/text/webvtt/WebvttCssStyle.java +++ b/library/src/main/java/com/google/android/exoplayer/text/webvtt/WebvttCssStyle.java @@ -20,6 +20,10 @@ import com.google.android.exoplayer.util.Util; import android.graphics.Typeface; import android.text.Layout; +import java.util.Arrays; +import java.util.Collections; +import java.util.List; + /** * Style object of a Css style block in a Webvtt file. * @@ -42,6 +46,13 @@ import android.text.Layout; private static final int OFF = 0; private static final int ON = 1; + // Selector properties. + private String targetId; + private String targetTag; + private List targetClasses; + private String targetVoice; + + // Style properties. private String fontFamily; private int fontColor; private boolean hasFontColor; @@ -60,6 +71,10 @@ import android.text.Layout; } public void reset() { + targetId = ""; + targetTag = ""; + targetClasses = Collections.emptyList(); + targetVoice = ""; fontFamily = null; hasFontColor = false; hasBackgroundColor = false; @@ -71,6 +86,59 @@ import android.text.Layout; textAlign = null; } + public void setTargetId(String targetId) { + this.targetId = targetId; + } + + public void setTargetTagName(String targetTag) { + this.targetTag = targetTag; + } + + public void setTargetClasses(String[] targetClasses) { + this.targetClasses = Arrays.asList(targetClasses); + } + + public void setTargetVoice(String targetVoice) { + this.targetVoice = targetVoice; + } + + /** + * Returns a value in a score system compliant with the CSS Specificity rules. + * + * @see CSS Cascading + * + * The score works as follows: + * + * + * @param id The id of the cue if present, {@code null} otherwise. + * @param tag Name of the tag, {@code null} if it refers to the entire cue. + * @param classes An array containing the classes the tag belongs to. Must not be null. + * @param voice Annotated voice if present, {@code null} otherwise. + * @return The score of the match, zero if there is no match. + */ + public int getSpecificityScore(String id, String tag, String[] classes, String voice) { + if (targetId.isEmpty() && targetTag.isEmpty() && targetClasses.isEmpty() + && targetVoice.isEmpty()) { + // The selector is universal. It matches with the minimum score. + return 1; + } + int score = 0; + score = updateScoreForMatch(score, targetId, id, 0x40000000); + score = updateScoreForMatch(score, targetTag, tag, 2); + score = updateScoreForMatch(score, targetVoice, voice, 4); + if (score == -1 || !Arrays.asList(classes).containsAll(targetClasses)) { + return 0; + } else { + score += targetClasses.size() * 4; + } + return score; + } + /** * Returns the style or {@link #UNSPECIFIED} when no style information is given. * @@ -214,5 +282,12 @@ import android.text.Layout; } } + private static int updateScoreForMatch(int currentScore, String target, String actual, + int score) { + if (target.isEmpty() || currentScore == -1) { + return currentScore; + } + return target.equals(actual) ? currentScore + score : -1; + } } diff --git a/library/src/main/java/com/google/android/exoplayer/text/webvtt/WebvttCueParser.java b/library/src/main/java/com/google/android/exoplayer/text/webvtt/WebvttCueParser.java index 60a942358a..7cf0e392f0 100644 --- a/library/src/main/java/com/google/android/exoplayer/text/webvtt/WebvttCueParser.java +++ b/library/src/main/java/com/google/android/exoplayer/text/webvtt/WebvttCueParser.java @@ -211,10 +211,10 @@ import java.util.regex.Pattern; } } // apply unclosed tags + applyStyleToText(spannedText, styleMap.get(UNIVERSAL_CUE_ID), 0, spannedText.length()); while (!startTagStack.isEmpty()) { applySpansForTag(startTagStack.pop(), spannedText, styleMap); } - applyStyleToText(spannedText, styleMap.get(UNIVERSAL_CUE_ID), 0, spannedText.length()); applyStyleToText(spannedText, styleMap.get(CUE_ID_PREFIX + id), 0, spannedText.length()); builder.setText(spannedText); }