From d28f3d781a125eae7e89956ff8e37fc7b94646c2 Mon Sep 17 00:00:00 2001 From: Abhinay Agarwal Date: Mon, 14 Mar 2022 14:04:33 +0000 Subject: [PATCH 1/5] 8282093: LineChart path incorrect when outside lower bound Reviewed-by: aghaisas --- .../java/javafx/scene/chart/AreaChart.java | 4 +- .../javafx/scene/chart/AreaChartTest.java | 181 +++++++++++++++- .../javafx/scene/chart/LineChartTest.java | 194 ++++++++++++++++-- .../controls/LineGraphBoundsSample.java | 167 +++++++++++++++ 4 files changed, 523 insertions(+), 23 deletions(-) create mode 100644 tests/manual/controls/LineGraphBoundsSample.java diff --git a/modules/javafx.controls/src/main/java/javafx/scene/chart/AreaChart.java b/modules/javafx.controls/src/main/java/javafx/scene/chart/AreaChart.java index 2e4a236b897..49baaff4264 100644 --- a/modules/javafx.controls/src/main/java/javafx/scene/chart/AreaChart.java +++ b/modules/javafx.controls/src/main/java/javafx/scene/chart/AreaChart.java @@ -467,8 +467,8 @@ static void makePaths(XYChart chart, Series series, if (x < dataXMin || y < dataYMin) { if (prevDataPoint == null) { prevDataPoint = new LineTo(x, y); - } else if ((sortX && prevDataPoint.getX() < x) || - (sortY && prevDataPoint.getY() < y)) + } else if ((sortX && prevDataPoint.getX() <= x) || + (sortY && prevDataPoint.getY() <= y)) { prevDataPoint.setX(x); prevDataPoint.setY(y); diff --git a/modules/javafx.controls/src/test/java/test/javafx/scene/chart/AreaChartTest.java b/modules/javafx.controls/src/test/java/test/javafx/scene/chart/AreaChartTest.java index 83c4879bc1e..a8fa54ecc9a 100644 --- a/modules/javafx.controls/src/test/java/test/javafx/scene/chart/AreaChartTest.java +++ b/modules/javafx.controls/src/test/java/test/javafx/scene/chart/AreaChartTest.java @@ -170,7 +170,27 @@ public void testSeriesRemoveAnimatedStyleClasses() { assertArrayEquals(convertSeriesDataToPoint2D(series1).toArray(), findDataPointsFromPathLine(ac).toArray()); } - @Test public void testPathOutsideXBoundsWithDuplicateXAndHigherY() { + @Test public void testPathOutsideXLowerBoundsWithDuplicateXAndHigherY() { + startApp(); + series1.getData().add(new XYChart.Data<>(-10d, 20d)); // lower bound is 0 + series1.getData().add(new XYChart.Data<>(-10d, 50d)); + ac.getData().addAll(series1); + pulse(); + + XYChart.Series expectedSeries = new XYChart.Series<>(); + expectedSeries.getData().addAll( + new XYChart.Data<>(-10d, 50d), + new XYChart.Data<>(10d, 10d), + new XYChart.Data<>(25d, 20d), + new XYChart.Data<>(30d, 15d), + new XYChart.Data<>(50d, 15d), + new XYChart.Data<>(80d, 10d) + ); + + assertArrayEquals(convertSeriesDataToPoint2D(expectedSeries).toArray(), findDataPointsFromPathLine(ac).toArray()); + } + + @Test public void testPathOutsideXUpperBoundsWithDuplicateXAndHigherY() { startApp(); series1.getData().add(new XYChart.Data<>(100d, 20d)); // upper bound is 90 series1.getData().add(new XYChart.Data<>(100d, 50d)); @@ -190,7 +210,27 @@ public void testSeriesRemoveAnimatedStyleClasses() { assertArrayEquals(convertSeriesDataToPoint2D(expectedSeries).toArray(), findDataPointsFromPathLine(ac).toArray()); } - @Test public void testPathOutsideXBoundsWithDuplicateXAndLowerY() { + @Test public void testPathOutsideXLowerBoundsWithDuplicateXAndLowerY() { + startApp(); + series1.getData().add(new XYChart.Data<>(-10d, 20d)); // lower bound is 0 + series1.getData().add(new XYChart.Data<>(-10d, 15d)); + ac.getData().addAll(series1); + pulse(); + + XYChart.Series expectedSeries = new XYChart.Series<>(); + expectedSeries.getData().addAll( + new XYChart.Data<>(-10d, 15d), + new XYChart.Data<>(10d, 10d), + new XYChart.Data<>(25d, 20d), + new XYChart.Data<>(30d, 15d), + new XYChart.Data<>(50d, 15d), + new XYChart.Data<>(80d, 10d) + ); + + assertArrayEquals(convertSeriesDataToPoint2D(expectedSeries).toArray(), findDataPointsFromPathLine(ac).toArray()); + } + + @Test public void testPathOutsideXUpperBoundsWithDuplicateXAndLowerY() { startApp(); series1.getData().add(new XYChart.Data<>(100d, 20d)); // upper bound is 90 series1.getData().add(new XYChart.Data<>(100d, 15d)); @@ -210,9 +250,31 @@ public void testSeriesRemoveAnimatedStyleClasses() { assertArrayEquals(convertSeriesDataToPoint2D(expectedSeries).toArray(), findDataPointsFromPathLine(ac).toArray()); } - @Test public void testPathOutsideYBoundsWithDuplicateYAndLowerX() { + @Test public void testPathOutsideYLowerBoundsWithDuplicateYAndLowerX() { + startApp(); + series1.getData().add(new XYChart.Data<>(85d, -10d)); // y-axis lower bound is 0 + series1.getData().add(new XYChart.Data<>(70d, -10d)); + ac.getData().addAll(series1); + pulse(); + + XYChart.Series expectedSeries = new XYChart.Series<>(); + expectedSeries.getData().addAll( + new XYChart.Data<>(10d, 10d), + new XYChart.Data<>(25d, 20d), + new XYChart.Data<>(30d, 15d), + new XYChart.Data<>(50d, 15d), + // Sorting policy in AreaChart is defaulted to X_AXIS. See AreaChart#makePaths + new XYChart.Data<>(70d, -10d), + new XYChart.Data<>(80d, 10d), + new XYChart.Data<>(85d, -10d) + ); + + assertArrayEquals(convertSeriesDataToPoint2D(expectedSeries).toArray(), findDataPointsFromPathLine(ac).toArray()); + } + + @Test public void testPathOutsideYUpperBoundsWithDuplicateYAndLowerX() { startApp(); - series1.getData().add(new XYChart.Data<>(85d, 40d)); + series1.getData().add(new XYChart.Data<>(85d, 40d)); // y-axis upper bound is 30 series1.getData().add(new XYChart.Data<>(70d, 40d)); ac.getData().addAll(series1); pulse(); @@ -232,7 +294,28 @@ public void testSeriesRemoveAnimatedStyleClasses() { assertArrayEquals(convertSeriesDataToPoint2D(expectedSeries).toArray(), findDataPointsFromPathLine(ac).toArray()); } - @Test public void testPathOutsideYBoundsWithDuplicateYAndHigherX() { + @Test public void testPathOutsideYLowerBoundsWithDuplicateYAndHigherX() { + startApp(); + series1.getData().add(new XYChart.Data<>(70d, -10d)); // lower bound is 30 + series1.getData().add(new XYChart.Data<>(85d, -10d)); + ac.getData().addAll(series1); + pulse(); + + XYChart.Series expectedSeries = new XYChart.Series<>(); + expectedSeries.getData().addAll( + new XYChart.Data<>(10d, 10d), + new XYChart.Data<>(25d, 20d), + new XYChart.Data<>(30d, 15d), + new XYChart.Data<>(50d, 15d), + new XYChart.Data<>(70d, -10d), + new XYChart.Data<>(80d, 10d), + new XYChart.Data<>(85d, -10d) + ); + + assertArrayEquals(convertSeriesDataToPoint2D(expectedSeries).toArray(), findDataPointsFromPathLine(ac).toArray()); + } + + @Test public void testPathOutsideYUpperBoundsWithDuplicateYAndHigherX() { startApp(); series1.getData().add(new XYChart.Data<>(70d, 32d)); // upper bound is 30 series1.getData().add(new XYChart.Data<>(85d, 32d)); @@ -253,7 +336,27 @@ public void testSeriesRemoveAnimatedStyleClasses() { assertArrayEquals(convertSeriesDataToPoint2D(expectedSeries).toArray(), findDataPointsFromPathLine(ac).toArray()); } - @Test public void testPathOutsideXAndYBoundsWithDuplicateXAndHigherY() { + @Test public void testPathOutsideXAndYLowerBoundsWithDuplicateXAndHigherY() { + startApp(); + series1.getData().add(new XYChart.Data<>(-10d, -40d)); // lower bound is 0,0 + series1.getData().add(new XYChart.Data<>(-10d, -30d)); + ac.getData().addAll(series1); + pulse(); + + XYChart.Series expectedSeries = new XYChart.Series<>(); + expectedSeries.getData().addAll( + new XYChart.Data<>(-10d, -30d), + new XYChart.Data<>(10d, 10d), + new XYChart.Data<>(25d, 20d), + new XYChart.Data<>(30d, 15d), + new XYChart.Data<>(50d, 15d), + new XYChart.Data<>(80d, 10d) + ); + + assertArrayEquals(convertSeriesDataToPoint2D(expectedSeries).toArray(), findDataPointsFromPathLine(ac).toArray()); + } + + @Test public void testPathOutsideXAndYUpperBoundsWithDuplicateXAndHigherY() { startApp(); series1.getData().add(new XYChart.Data<>(95d, 35d)); // upper bound is 90,30 series1.getData().add(new XYChart.Data<>(95d, 40d)); @@ -273,7 +376,27 @@ public void testSeriesRemoveAnimatedStyleClasses() { assertArrayEquals(convertSeriesDataToPoint2D(expectedSeries).toArray(), findDataPointsFromPathLine(ac).toArray()); } - @Test public void testPathOutsideXAndYBoundsWithDuplicateXAndLowerY() { + @Test public void testPathOutsideXAndYLowerBoundsWithDuplicateXAndLowerY() { + startApp(); + series1.getData().add(new XYChart.Data<>(-10d, -30d)); // lower bound is 0,0 + series1.getData().add(new XYChart.Data<>(-10d, -40d)); + ac.getData().addAll(series1); + pulse(); + + XYChart.Series expectedSeries = new XYChart.Series<>(); + expectedSeries.getData().addAll( + new XYChart.Data<>(-10d, -40d), + new XYChart.Data<>(10d, 10d), + new XYChart.Data<>(25d, 20d), + new XYChart.Data<>(30d, 15d), + new XYChart.Data<>(50d, 15d), + new XYChart.Data<>(80d, 10d) + ); + + assertArrayEquals(convertSeriesDataToPoint2D(expectedSeries).toArray(), findDataPointsFromPathLine(ac).toArray()); + } + + @Test public void testPathOutsideXAndYUpperBoundsWithDuplicateXAndLowerY() { startApp(); series1.getData().add(new XYChart.Data<>(95d, 40d)); // upper bound is 90,30 series1.getData().add(new XYChart.Data<>(95d, 35d)); @@ -293,7 +416,27 @@ public void testSeriesRemoveAnimatedStyleClasses() { assertArrayEquals(convertSeriesDataToPoint2D(expectedSeries).toArray(), findDataPointsFromPathLine(ac).toArray()); } - @Test public void testPathOutsideXAndYBoundsWithDuplicateYAndHigherX() { + @Test public void testPathOutsideXAndYLowerBoundsWithDuplicateYAndHigherX() { + startApp(); + series1.getData().add(new XYChart.Data<>(-20d, -30d)); // lower bound is 0,0 + series1.getData().add(new XYChart.Data<>(-10d, -30d)); + ac.getData().addAll(series1); + pulse(); + + XYChart.Series expectedSeries = new XYChart.Series<>(); + expectedSeries.getData().addAll( + new XYChart.Data<>(-10d, -30d), + new XYChart.Data<>(10d, 10d), + new XYChart.Data<>(25d, 20d), + new XYChart.Data<>(30d, 15d), + new XYChart.Data<>(50d, 15d), + new XYChart.Data<>(80d, 10d) + ); + + assertArrayEquals(convertSeriesDataToPoint2D(expectedSeries).toArray(), findDataPointsFromPathLine(ac).toArray()); + } + + @Test public void testPathOutsideXAndYUpperBoundsWithDuplicateYAndHigherX() { startApp(); series1.getData().add(new XYChart.Data<>(95d, 32d)); // upper bound is 90,30 series1.getData().add(new XYChart.Data<>(100d, 32d)); @@ -313,7 +456,27 @@ public void testSeriesRemoveAnimatedStyleClasses() { assertArrayEquals(convertSeriesDataToPoint2D(expectedSeries).toArray(), findDataPointsFromPathLine(ac).toArray()); } - @Test public void testPathOutsideXAndYBoundsWithDuplicateYAndLowerX() { + @Test public void testPathOutsideXAndYLowerBoundsWithDuplicateYAndLowerX() { + startApp(); + series1.getData().add(new XYChart.Data<>(-10d, -30d)); // lower bound is 0,0 + series1.getData().add(new XYChart.Data<>(-20d, -30d)); + ac.getData().addAll(series1); + pulse(); + + XYChart.Series expectedSeries = new XYChart.Series<>(); + expectedSeries.getData().addAll( + new XYChart.Data<>(-10d, -30d), + new XYChart.Data<>(10d, 10d), + new XYChart.Data<>(25d, 20d), + new XYChart.Data<>(30d, 15d), + new XYChart.Data<>(50d, 15d), + new XYChart.Data<>(80d, 10d) + ); + + assertArrayEquals(convertSeriesDataToPoint2D(expectedSeries).toArray(), findDataPointsFromPathLine(ac).toArray()); + } + + @Test public void testPathOutsideXAndYUpperBoundsWithDuplicateYAndLowerX() { startApp(); series1.getData().add(new XYChart.Data<>(100d, 40d)); // upper bound is 90,30 series1.getData().add(new XYChart.Data<>(95d, 40d)); diff --git a/modules/javafx.controls/src/test/java/test/javafx/scene/chart/LineChartTest.java b/modules/javafx.controls/src/test/java/test/javafx/scene/chart/LineChartTest.java index 90cbe8dd3e5..f066f9bdb3b 100644 --- a/modules/javafx.controls/src/test/java/test/javafx/scene/chart/LineChartTest.java +++ b/modules/javafx.controls/src/test/java/test/javafx/scene/chart/LineChartTest.java @@ -339,7 +339,29 @@ public void testSeriesRemoveAnimatedStyleClasses() { assertArrayEquals(convertSeriesDataToPoint2D(expectedSeries).toArray(), findDataPointsFromPathLine(lineChart).toArray()); } - @Test public void testPathOutsideXBoundsWithDuplicateXAndHigherYWithSortYAxis() { + @Test public void testPathOutsideXLowerBoundsWithDuplicateXAndHigherYWithSortYAxis() { + startApp(); + series1.getData().add(new XYChart.Data(-10d, 20d)); // lower bound is 0 + series1.getData().add(new XYChart.Data(-10d, 50d)); + lineChart.getData().addAll(series1); + lineChart.setAxisSortingPolicy(LineChart.SortingPolicy.Y_AXIS); + pulse(); + + XYChart.Series expectedSeries = new XYChart.Series<>(); + expectedSeries.getData().addAll( + new XYChart.Data<>(-10d, 50d), + new XYChart.Data<>(25d, 20d), + new XYChart.Data<>(-10d, 20d), + new XYChart.Data<>(30d, 15d), + new XYChart.Data<>(50d, 15d), + new XYChart.Data<>(10d, 10d), + new XYChart.Data<>(80d, 10d) + ); + + assertArrayEquals(convertSeriesDataToPoint2D(expectedSeries).toArray(), findDataPointsFromPathLine(lineChart).toArray()); + } + + @Test public void testPathOutsideXUpperBoundsWithDuplicateXAndHigherYWithSortYAxis() { startApp(); series1.getData().add(new XYChart.Data(100d, 20d)); // upper bound is 90 series1.getData().add(new XYChart.Data(100d, 50d)); @@ -361,7 +383,29 @@ public void testSeriesRemoveAnimatedStyleClasses() { assertArrayEquals(convertSeriesDataToPoint2D(expectedSeries).toArray(), findDataPointsFromPathLine(lineChart).toArray()); } - @Test public void testPathOutsideXBoundsWithDuplicateXAndLowerYWithSortYAxis() { + @Test public void testPathOutsideXLowerBoundsWithDuplicateXAndLowerYWithSortYAxis() { + startApp(); + series1.getData().add(new XYChart.Data(-10d, 20d)); // lower bound is 0 + series1.getData().add(new XYChart.Data(-10d, 15d)); + lineChart.getData().addAll(series1); + lineChart.setAxisSortingPolicy(LineChart.SortingPolicy.Y_AXIS); + pulse(); + + XYChart.Series expectedSeries = new XYChart.Series<>(); + expectedSeries.getData().addAll( + new XYChart.Data<>(25d, 20d), + new XYChart.Data<>(-10d, 20d), + new XYChart.Data<>(30d, 15d), + new XYChart.Data<>(50d, 15d), + new XYChart.Data<>(-10d, 15d), + new XYChart.Data<>(10d, 10d), + new XYChart.Data<>(80d, 10d) + ); + + assertArrayEquals(convertSeriesDataToPoint2D(expectedSeries).toArray(), findDataPointsFromPathLine(lineChart).toArray()); + } + + @Test public void testPathOutsideXUpperBoundsWithDuplicateXAndLowerYWithSortYAxis() { startApp(); series1.getData().add(new XYChart.Data(100d, 20d)); // upper bound is 90 series1.getData().add(new XYChart.Data(100d, 15d)); @@ -383,7 +427,28 @@ public void testSeriesRemoveAnimatedStyleClasses() { assertArrayEquals(convertSeriesDataToPoint2D(expectedSeries).toArray(), findDataPointsFromPathLine(lineChart).toArray()); } - @Test public void testPathOutsideYBoundsWithDuplicateYAndHigherXWithSortYAxis() { + @Test public void testPathOutsideYLowerBoundsWithDuplicateYAndHigherXWithSortYAxis() { + startApp(); + series1.getData().add(new XYChart.Data(80d, -10d)); // lower bound is 0 + series1.getData().add(new XYChart.Data(90d, -10d)); + lineChart.getData().addAll(series1); + lineChart.setAxisSortingPolicy(LineChart.SortingPolicy.Y_AXIS); + pulse(); + + XYChart.Series expectedSeries = new XYChart.Series<>(); + expectedSeries.getData().addAll( + new XYChart.Data<>(25d, 20d), + new XYChart.Data<>(30d, 15d), + new XYChart.Data<>(50d, 15d), + new XYChart.Data<>(10d, 10d), + new XYChart.Data<>(80d, 10d), + new XYChart.Data<>(80d, -10d) + ); + + assertArrayEquals(convertSeriesDataToPoint2D(expectedSeries).toArray(), findDataPointsFromPathLine(lineChart).toArray()); + } + + @Test public void testPathOutsideYUpperBoundsWithDuplicateYAndHigherXWithSortYAxis() { startApp(); series1.getData().add(new XYChart.Data(80d, 32d)); // upper bound is 30 series1.getData().add(new XYChart.Data(90d, 32d)); @@ -393,7 +458,7 @@ public void testSeriesRemoveAnimatedStyleClasses() { XYChart.Series expectedSeries = new XYChart.Series<>(); expectedSeries.getData().addAll( - new XYChart.Data<>(80d, 32d), + new XYChart.Data<>(90d, 32d), new XYChart.Data<>(25d, 20d), new XYChart.Data<>(30d, 15d), new XYChart.Data<>(50d, 15d), @@ -404,7 +469,27 @@ public void testSeriesRemoveAnimatedStyleClasses() { assertArrayEquals(convertSeriesDataToPoint2D(expectedSeries).toArray(), findDataPointsFromPathLine(lineChart).toArray()); } - @Test public void testPathOutsideYBoundsWithDuplicateYAndLowerXWithSortYAxis() { + @Test public void testPathOutsideYLowerBoundsWithDuplicateYAndLowerXWithSortYAxis() { + startApp(); + series1.getData().add(new XYChart.Data(80d, -10d)); // lower bound is 0 + series1.getData().add(new XYChart.Data(70d, -10d)); + lineChart.getData().addAll(series1); + lineChart.setAxisSortingPolicy(LineChart.SortingPolicy.Y_AXIS); + pulse(); + + XYChart.Series expectedSeries = new XYChart.Series<>(); + expectedSeries.getData().addAll( + new XYChart.Data<>(25d, 20d), + new XYChart.Data<>(30d, 15d), + new XYChart.Data<>(50d, 15d), + new XYChart.Data<>(10d, 10d), + new XYChart.Data<>(80d, 10d), + new XYChart.Data<>(80d, -10d) + ); + assertArrayEquals(convertSeriesDataToPoint2D(expectedSeries).toArray(), findDataPointsFromPathLine(lineChart).toArray()); + } + + @Test public void testPathOutsideYUpperBoundsWithDuplicateYAndLowerXWithSortYAxis() { startApp(); series1.getData().add(new XYChart.Data(80d, 40d)); // upper bound is 30 series1.getData().add(new XYChart.Data(70d, 40d)); @@ -414,7 +499,7 @@ public void testSeriesRemoveAnimatedStyleClasses() { XYChart.Series expectedSeries = new XYChart.Series<>(); expectedSeries.getData().addAll( - new XYChart.Data<>(80d, 40d), + new XYChart.Data<>(70d, 40d), new XYChart.Data<>(25d, 20d), new XYChart.Data<>(30d, 15d), new XYChart.Data<>(50d, 15d), @@ -425,7 +510,29 @@ public void testSeriesRemoveAnimatedStyleClasses() { assertArrayEquals(convertSeriesDataToPoint2D(expectedSeries).toArray(), findDataPointsFromPathLine(lineChart).toArray()); } - @Test public void testPathOutsideXAndYBoundsWithDuplicateXAndHigherYWithSortYAxis() { + @Test public void testPathOutsideXAndYLowerBoundsWithDuplicateXAndHigherYWithSortYAxis() { + startApp(); + series1.getData().add(new XYChart.Data<>(95d, -10d)); // lower bound is 0,0 + series1.getData().add(new XYChart.Data<>(95d, -5d)); + lineChart.getData().addAll(series1); + lineChart.setAxisSortingPolicy(LineChart.SortingPolicy.Y_AXIS); + pulse(); + + XYChart.Series expectedSeries = new XYChart.Series<>(); + expectedSeries.getData().addAll( + new XYChart.Data<>(25d, 20d), + new XYChart.Data<>(30d, 15d), + new XYChart.Data<>(50d, 15d), + new XYChart.Data<>(10d, 10d), + new XYChart.Data<>(80d, 10d), + new XYChart.Data<>(95d, -5d)/*, + new XYChart.Data<>(95d, -10d)*/ + ); + + assertArrayEquals(convertSeriesDataToPoint2D(expectedSeries).toArray(), findDataPointsFromPathLine(lineChart).toArray()); + } + + @Test public void testPathOutsideXAndYUpperBoundsWithDuplicateXAndHigherYWithSortYAxis() { startApp(); series1.getData().add(new XYChart.Data<>(95d, 35d)); // upper bound is 90,30 series1.getData().add(new XYChart.Data<>(95d, 40d)); @@ -446,7 +553,28 @@ public void testSeriesRemoveAnimatedStyleClasses() { assertArrayEquals(convertSeriesDataToPoint2D(expectedSeries).toArray(), findDataPointsFromPathLine(lineChart).toArray()); } - @Test public void testPathOutsideXAndYBoundsWithDuplicateXAndLowerYWithSortYAxis() { + @Test public void testPathOutsideXAndYLowerBoundsWithDuplicateXAndLowerYWithSortYAxis() { + startApp(); + series1.getData().add(new XYChart.Data<>(-10d, -10d)); // lower bound is 0,0 + series1.getData().add(new XYChart.Data<>(-10d, -20d)); + lineChart.getData().addAll(series1); + lineChart.setAxisSortingPolicy(LineChart.SortingPolicy.Y_AXIS); + pulse(); + + XYChart.Series expectedSeries = new XYChart.Series<>(); + expectedSeries.getData().addAll( + new XYChart.Data<>(25d, 20d), + new XYChart.Data<>(30d, 15d), + new XYChart.Data<>(50d, 15d), + new XYChart.Data<>(10d, 10d), + new XYChart.Data<>(80d, 10d), + new XYChart.Data<>(-10d, -10d) + ); + + assertArrayEquals(convertSeriesDataToPoint2D(expectedSeries).toArray(), findDataPointsFromPathLine(lineChart).toArray()); + } + + @Test public void testPathOutsideXAndYUpperBoundsWithDuplicateXAndLowerYWithSortYAxis() { startApp(); series1.getData().add(new XYChart.Data<>(95d, 40d)); // upper bound is 90,30 series1.getData().add(new XYChart.Data<>(95d, 35d)); @@ -467,7 +595,28 @@ public void testSeriesRemoveAnimatedStyleClasses() { assertArrayEquals(convertSeriesDataToPoint2D(expectedSeries).toArray(), findDataPointsFromPathLine(lineChart).toArray()); } - @Test public void testPathOutsideXAndYBoundsWithDuplicateYAndHigherXWithSortYAxis() { + @Test public void testPathOutsideXAndYLowerBoundsWithDuplicateYAndHigherXWithSortYAxis() { + startApp(); + series1.getData().add(new XYChart.Data<>(-15d, -10d)); // lower bound is 0,0 + series1.getData().add(new XYChart.Data<>(-10d, -10d)); + lineChart.getData().addAll(series1); + lineChart.setAxisSortingPolicy(LineChart.SortingPolicy.Y_AXIS); + pulse(); + + XYChart.Series expectedSeries = new XYChart.Series<>(); + expectedSeries.getData().addAll( + new XYChart.Data<>(25d, 20d), + new XYChart.Data<>(30d, 15d), + new XYChart.Data<>(50d, 15d), + new XYChart.Data<>(10d, 10d), + new XYChart.Data<>(80d, 10d), + new XYChart.Data<>(-15d, -10d) + ); + + assertArrayEquals(convertSeriesDataToPoint2D(expectedSeries).toArray(), findDataPointsFromPathLine(lineChart).toArray()); + } + + @Test public void testPathOutsideXAndYUpperBoundsWithDuplicateYAndHigherXWithSortYAxis() { startApp(); series1.getData().add(new XYChart.Data<>(95d, 32d)); // upper bound is 90,30 series1.getData().add(new XYChart.Data<>(100d, 32d)); @@ -477,7 +626,7 @@ public void testSeriesRemoveAnimatedStyleClasses() { XYChart.Series expectedSeries = new XYChart.Series<>(); expectedSeries.getData().addAll( - new XYChart.Data<>(95d, 32d), + new XYChart.Data<>(100d, 32d), new XYChart.Data<>(25d, 20d), new XYChart.Data<>(30d, 15d), new XYChart.Data<>(50d, 15d), @@ -488,7 +637,28 @@ public void testSeriesRemoveAnimatedStyleClasses() { assertArrayEquals(convertSeriesDataToPoint2D(expectedSeries).toArray(), findDataPointsFromPathLine(lineChart).toArray()); } - @Test public void testPathOutsideXAndYBoundsWithDuplicateYAndLowerXWithSortYAxis() { + @Test public void testPathOutsideXAndYLowerBoundsWithDuplicateYAndLowerXWithSortYAxis() { + startApp(); + series1.getData().add(new XYChart.Data<>(-10d, -10d)); // lower bound is 0,0 + series1.getData().add(new XYChart.Data<>(-15d, -10d)); + lineChart.getData().addAll(series1); + lineChart.setAxisSortingPolicy(LineChart.SortingPolicy.Y_AXIS); + pulse(); + + XYChart.Series expectedSeries = new XYChart.Series<>(); + expectedSeries.getData().addAll( + new XYChart.Data<>(25d, 20d), + new XYChart.Data<>(30d, 15d), + new XYChart.Data<>(50d, 15d), + new XYChart.Data<>(10d, 10d), + new XYChart.Data<>(80d, 10d), + new XYChart.Data<>(-10d, -10d) + ); + + assertArrayEquals(convertSeriesDataToPoint2D(expectedSeries).toArray(), findDataPointsFromPathLine(lineChart).toArray()); + } + + @Test public void testPathOutsideXAndYUpperBoundsWithDuplicateYAndLowerXWithSortYAxis() { startApp(); series1.getData().add(new XYChart.Data<>(100d, 40d)); // upper bound is 90,30 series1.getData().add(new XYChart.Data<>(95d, 40d)); @@ -498,7 +668,7 @@ public void testSeriesRemoveAnimatedStyleClasses() { XYChart.Series expectedSeries = new XYChart.Series<>(); expectedSeries.getData().addAll( - new XYChart.Data<>(100d, 40d), + new XYChart.Data<>(95d, 40d), new XYChart.Data<>(25d, 20d), new XYChart.Data<>(30d, 15d), new XYChart.Data<>(50d, 15d), diff --git a/tests/manual/controls/LineGraphBoundsSample.java b/tests/manual/controls/LineGraphBoundsSample.java new file mode 100644 index 00000000000..a0759cbd24d --- /dev/null +++ b/tests/manual/controls/LineGraphBoundsSample.java @@ -0,0 +1,167 @@ +/* + * Copyright (c) 2022, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +import javafx.application.Application; +import javafx.beans.property.DoubleProperty; +import javafx.beans.property.SimpleDoubleProperty; +import javafx.collections.FXCollections; +import javafx.collections.ObservableList; +import javafx.geometry.HPos; +import javafx.geometry.Insets; +import javafx.geometry.Point2D; +import javafx.geometry.Pos; +import javafx.scene.Scene; +import javafx.scene.chart.LineChart; +import javafx.scene.chart.NumberAxis; +import javafx.scene.chart.XYChart; +import javafx.scene.control.Button; +import javafx.scene.control.ComboBox; +import javafx.scene.control.Label; +import javafx.scene.control.TextField; +import javafx.scene.layout.BorderPane; +import javafx.scene.layout.GridPane; +import javafx.scene.layout.HBox; +import javafx.stage.Stage; + +import java.util.ArrayList; +import java.util.List; + +public class LineGraphBoundsSample extends Application { + + @Override + public void start(Stage primaryStage) { + NumberAxis yAxis = new NumberAxis(); + NumberAxis xAxis = new NumberAxis(); + LineChart lineChart = new LineChart<>(xAxis, yAxis); + + DoubleProperty lowerBound = new SimpleDoubleProperty(0); + DoubleProperty upperBound = new SimpleDoubleProperty(2); + + final ComboBox axisSelection = new ComboBox<>(FXCollections.observableArrayList("X-Axis", "Y-Axis")); + axisSelection.getSelectionModel().selectedIndexProperty().addListener((o, ov, nv) -> { + switch (nv.intValue()) { + case 0: + lineChart.setAxisSortingPolicy(LineChart.SortingPolicy.X_AXIS); + lineChart.setData(createData(LineChart.SortingPolicy.X_AXIS)); + lineChart.getXAxis().setAutoRanging(false); + lineChart.getYAxis().setAutoRanging(true); + ((NumberAxis) lineChart.getXAxis()).lowerBoundProperty().bind(lowerBound); + ((NumberAxis) lineChart.getXAxis()).upperBoundProperty().bind(upperBound); + ((NumberAxis) lineChart.getYAxis()).lowerBoundProperty().unbind(); + ((NumberAxis) lineChart.getYAxis()).upperBoundProperty().unbind(); + break; + case 1: + lineChart.setAxisSortingPolicy(LineChart.SortingPolicy.Y_AXIS); + lineChart.setData(createData(LineChart.SortingPolicy.Y_AXIS)); + lineChart.getXAxis().setAutoRanging(true); + lineChart.getYAxis().setAutoRanging(false); + ((NumberAxis) lineChart.getXAxis()).lowerBoundProperty().unbind(); + ((NumberAxis) lineChart.getXAxis()).upperBoundProperty().unbind(); + ((NumberAxis) lineChart.getYAxis()).lowerBoundProperty().bind(lowerBound); + ((NumberAxis) lineChart.getYAxis()).upperBoundProperty().bind(upperBound); + break; + } + lowerBound.set(0); + upperBound.set(2); + }); + + Button decrement = new Button("Decrease Bound"); + decrement.setOnAction(e -> { + final double CHANGE_VALUE = -0.1; + lowerBound.set(lowerBound.get() + CHANGE_VALUE); + upperBound.set(upperBound.get() + CHANGE_VALUE); + }); + + Button increment = new Button("Increase Bound"); + increment.setOnAction(e -> { + final double CHANGE_VALUE = 0.1; + lowerBound.set(lowerBound.get() + CHANGE_VALUE); + upperBound.set(upperBound.get() + CHANGE_VALUE); + }); + + final TextField lowerBoundTextField = new TextField(); + lowerBoundTextField.setEditable(false); + lowerBoundTextField.textProperty().bind(lowerBound.asString("%.2f")); + + final TextField upperBoundTextField = new TextField(); + upperBoundTextField.setEditable(false); + upperBoundTextField.textProperty().bind(upperBound.asString("%.2f")); + + final BorderPane root = new BorderPane(); + final GridPane gridPane = new GridPane(); + gridPane.setVgap(5); + gridPane.setHgap(5); + gridPane.setPadding(new Insets(10)); + gridPane.add(new Label("Sorting Policy: "), 0, 0); + gridPane.add(axisSelection, 1, 0); + gridPane.add(new Label("Lower Bound: "), 2, 0); + gridPane.add(new Label("Upper Bound: "), 2, 1); + gridPane.add(lowerBoundTextField, 3, 0); + gridPane.add(upperBoundTextField, 3, 1); + final HBox buttons = new HBox(10, decrement, increment); + buttons.setAlignment(Pos.CENTER); + GridPane.setHalignment(buttons, HPos.CENTER); + gridPane.add(buttons, 0, 2, 4,1); + root.setTop(gridPane); + root.setCenter(lineChart); + + Scene scene = new Scene(root); + primaryStage.setScene(scene); + primaryStage.show(); + + axisSelection.getSelectionModel().select(0); + } + + private ObservableList> createData(LineChart.SortingPolicy sortingPolicy) { + XYChart.Series series = new XYChart.Series<>(); + List points = new ArrayList<>(); + switch (sortingPolicy) { + case X_AXIS: + points.addAll(List.of( + new Point2D(0.4, 0.5), + new Point2D(0.8, 0.5), + new Point2D(0.8, 1.0), + new Point2D(1.0, 1.0), + new Point2D(1.0, 0.8), + new Point2D(1.5, 0.8) + )); + break; + case Y_AXIS: + points.addAll(List.of( + new Point2D(0.5, 1.3), + new Point2D(0.5, 0.9), + new Point2D(1.0, 0.9), + new Point2D(1.0, 0.7), + new Point2D(0.8, 0.7), + new Point2D(0.8, 0.5) + )); + break; + } + points.forEach(point -> { + series.getData().add(new XYChart.Data<>(point.getX(), point.getY())); + }); + return FXCollections.observableArrayList(series); + } +} From df43d2bdff884f6ea2f2c1f908504260db04e423 Mon Sep 17 00:00:00 2001 From: Abhinay Agarwal Date: Tue, 15 Mar 2022 07:42:30 +0000 Subject: [PATCH 2/5] 8282766: Create release notes for JavaFX 18 Reviewed-by: kcr, jvos --- doc-files/release-notes-18.md | 110 ++++++++++++++++++++++++++++++++++ 1 file changed, 110 insertions(+) create mode 100644 doc-files/release-notes-18.md diff --git a/doc-files/release-notes-18.md b/doc-files/release-notes-18.md new file mode 100644 index 00000000000..c2803664634 --- /dev/null +++ b/doc-files/release-notes-18.md @@ -0,0 +1,110 @@ +# Release Notes for JavaFX 18 + +## Introduction + +The following notes describe important changes and information about this release. In some cases, the descriptions provide links to additional detailed information about an issue or a change. + +As of JDK 11 the JavaFX modules are delivered separately from the JDK. These release notes cover the standalone JavaFX 18 release. JavaFX 18 requires JDK 11 or later. + +## Important Changes + +### Deprecate JavaFX GTK 2 library for removal + +The JavaFX GTK 2 library is deprecated and will be removed in a future release. The JavaFX runtime issues a warning if the GTK 2 library is requested on the command line via `java -Djdk.gtk.version=2`. +The JavaFX runtime also issues a warning if the GTK 2 library is selected as a fallback, which happens if the GTK 3 library cannot be loaded. Application developers should avoid requesting the GTK 2 library. + +See [JDK-8273089](https://bugs.openjdk.java.net/browse/JDK-8273089) for more information. + +## List of Enhancements + +Issue key|Summary|Subcomponent +---------|-------|------------ +[JDK-8267472](https://bugs.openjdk.java.net/browse/JDK-8267472)|JavaFX modules to include version information|build +[JDK-8172095](https://bugs.openjdk.java.net/browse/JDK-8172095)|Let Node.managed become CSS-styleable|controls +[JDK-8234921](https://bugs.openjdk.java.net/browse/JDK-8234921)|Add DirectionalLight to the selection of 3D light types|graphics +[JDK-8272870](https://bugs.openjdk.java.net/browse/JDK-8272870)|Add convenience factory methods for Border and Background|graphics +[JDK-8278595](https://bugs.openjdk.java.net/browse/JDK-8278595)|Provide more information when a pipeline can't be used|graphics +[JDK-8278860](https://bugs.openjdk.java.net/browse/JDK-8278860)|Streamline properties for Monocle|graphics +[JDK-8273096](https://bugs.openjdk.java.net/browse/JDK-8273096)|Add support for H.265/HEVC to JavaFX Media|media +[JDK-8214158](https://bugs.openjdk.java.net/browse/JDK-8214158)|Implement HostServices.showDocument on macOS without calling AWT|other +[JDK-8090547](https://bugs.openjdk.java.net/browse/JDK-8090547)|Allow for transparent backgrounds in WebView|web +[JDK-8273089](https://bugs.openjdk.java.net/browse/JDK-8273089)|Deprecate JavaFX GTK 2 library for removal|window-toolkit + +## List of Fixed Bugs + +Issue key|Summary|Subcomponent +---------|-------|------------ +[JDK-8203463](https://bugs.openjdk.java.net/browse/JDK-8203463)|[Accessibility, Narrator] NPE in TableView|accessibility +[JDK-8273969](https://bugs.openjdk.java.net/browse/JDK-8273969)|Memory Leak on the Runnable provided to Platform.startup|application-lifecycle +[JDK-8270838](https://bugs.openjdk.java.net/browse/JDK-8270838)|Remove deprecated protected access members from DateTimeStringConverter|base +[JDK-8273138](https://bugs.openjdk.java.net/browse/JDK-8273138)|BidirectionalBinding fails to observe changes of invalid properties|base +[JDK-8273754](https://bugs.openjdk.java.net/browse/JDK-8273754)|Re-introduce Automatic-Module-Name in empty jars|build +[JDK-8278260](https://bugs.openjdk.java.net/browse/JDK-8278260)|JavaFX shared libraries not stripped on Linux or macOS|build +[JDK-8089398](https://bugs.openjdk.java.net/browse/JDK-8089398)|[ChoiceBox, ComboBox] throws NPE on setting value on null selectionModel|controls +[JDK-8090158](https://bugs.openjdk.java.net/browse/JDK-8090158)|Wrong implementation of adjustValue in scrollBars|controls +[JDK-8187474](https://bugs.openjdk.java.net/browse/JDK-8187474)|Tree-/TableCell, TreeCell: editingCell/Item not updated in cell.startEdit|controls +[JDK-8188026](https://bugs.openjdk.java.net/browse/JDK-8188026)|TextFieldXXCell: NPE on calling startEdit|controls +[JDK-8188027](https://bugs.openjdk.java.net/browse/JDK-8188027)|List/TableCell: must not fire event in startEdit if already editing|controls +[JDK-8191995](https://bugs.openjdk.java.net/browse/JDK-8191995)|Regression: DatePicker must commit on focusLost|controls +[JDK-8197991](https://bugs.openjdk.java.net/browse/JDK-8197991)|Selecting many items in a TableView is very slow|controls +[JDK-8205915](https://bugs.openjdk.java.net/browse/JDK-8205915)|[macOS] Accelerator assigned to button in dialog fires menuItem in owning stage|controls +[JDK-8231644](https://bugs.openjdk.java.net/browse/JDK-8231644)|TreeTableView Regression: Indentation wrong using Label as column content type|controls +[JDK-8240506](https://bugs.openjdk.java.net/browse/JDK-8240506)|TextFieldSkin/Behavior: misbehavior on switching skin|controls +[JDK-8244419](https://bugs.openjdk.java.net/browse/JDK-8244419)|TextAreaSkin: throws UnsupportedOperation on dispose|controls +[JDK-8268295](https://bugs.openjdk.java.net/browse/JDK-8268295)|Tree- and TableCell sub implementations should respect the row editability|controls +[JDK-8269081](https://bugs.openjdk.java.net/browse/JDK-8269081)|Tree/ListViewSkin: must remove flow on dispose|controls +[JDK-8269871](https://bugs.openjdk.java.net/browse/JDK-8269871)|CellEditEvent: must not throw NPE in accessors|controls +[JDK-8271474](https://bugs.openjdk.java.net/browse/JDK-8271474)|Tree-/TableCell: inconsistent edit event firing pattern|controls +[JDK-8271484](https://bugs.openjdk.java.net/browse/JDK-8271484)|Tree-/TableCell: NPE when accessing edit event from startEdit|controls +[JDK-8272118](https://bugs.openjdk.java.net/browse/JDK-8272118)|ListViewSkin et al: must not cancel edit on scrolling|controls +[JDK-8273071](https://bugs.openjdk.java.net/browse/JDK-8273071)|SeparatorSkin: must remove child on dispose|controls +[JDK-8273324](https://bugs.openjdk.java.net/browse/JDK-8273324)|IllegalArgumentException: fromIndex(0) > toIndex(-1) after clear and select TableCell|controls +[JDK-8274022](https://bugs.openjdk.java.net/browse/JDK-8274022)|Additional Memory Leak in ControlAcceleratorSupport|controls +[JDK-8274061](https://bugs.openjdk.java.net/browse/JDK-8274061)|Tree-/TableRowSkin: misbehavior on switching skin|controls +[JDK-8274137](https://bugs.openjdk.java.net/browse/JDK-8274137)|TableView scrollbar/header misaligned when reloading data|controls +[JDK-8274854](https://bugs.openjdk.java.net/browse/JDK-8274854)|Mnemonics for menu containing numeric text not working|controls +[JDK-8274433](https://bugs.openjdk.java.net/browse/JDK-8274433)|All Cells: misbehavior of startEdit|controls +[JDK-8274699](https://bugs.openjdk.java.net/browse/JDK-8274699)|Certain blend modes cannot be set from CSS|controls +[JDK-8274669](https://bugs.openjdk.java.net/browse/JDK-8274669)|Dialog sometimes ignores max height|controls +[JDK-8275911](https://bugs.openjdk.java.net/browse/JDK-8275911)|Keyboard doesn't show when tapping inside an iOS text input control|controls +[JDK-8276167](https://bugs.openjdk.java.net/browse/JDK-8276167)|VirtualFlow.scrollToTop doesn't scroll to the top of the last element|controls +[JDK-8276313](https://bugs.openjdk.java.net/browse/JDK-8276313)|ScrollPane scroll delta incorrectly depends on content height|controls +[JDK-8276553](https://bugs.openjdk.java.net/browse/JDK-8276553)|ListView scrollTo() is broken after fix for JDK-8089589|controls +[JDK-8281207](https://bugs.openjdk.java.net/browse/JDK-8281207)|TableView scrollTo() will not show last row for a custom cell factory.|controls +[JDK-8232812](https://bugs.openjdk.java.net/browse/JDK-8232812)|[MacOS] Double click title bar does not restore window size|graphics +[JDK-8236689](https://bugs.openjdk.java.net/browse/JDK-8236689)|macOS 10.15 Catalina: LCD text renders badly|graphics +[JDK-8254956](https://bugs.openjdk.java.net/browse/JDK-8254956)|[REDO] Memoryleak: Closed focused Stages are not collected with Monocle|graphics +[JDK-8255015](https://bugs.openjdk.java.net/browse/JDK-8255015)|Inconsistent illumination of 3D shape by PointLight|graphics +[JDK-8269374](https://bugs.openjdk.java.net/browse/JDK-8269374)|Menu inoperable after setting stage to second monitor|graphics +[JDK-8269638](https://bugs.openjdk.java.net/browse/JDK-8269638)|Property methods, setters, and getters in printing API should be final|graphics +[JDK-8269639](https://bugs.openjdk.java.net/browse/JDK-8269639)|[macos] Calling stage.setY(0) twice causes wrong popups location|graphics +[JDK-8276490](https://bugs.openjdk.java.net/browse/JDK-8276490)|Incorrect path for duplicate x and y values, when path falls outside axis bound|graphics +[JDK-8276915](https://bugs.openjdk.java.net/browse/JDK-8276915)|Crash on iOS 15.1 in GlassRunnable::dealloc|graphics +[JDK-8278905](https://bugs.openjdk.java.net/browse/JDK-8278905)|JavaFX: EnumConverter has a typo in the toString method|graphics +[JDK-8279328](https://bugs.openjdk.java.net/browse/JDK-8279328)|CssParser uses default charset instead of UTF-8|graphics +[JDK-8253351](https://bugs.openjdk.java.net/browse/JDK-8253351)|MediaPlayer does not display an mp4 if there no speakers connected to the PC's|media +[JDK-8268718](https://bugs.openjdk.java.net/browse/JDK-8268718)|[macos] Video stops, but audio continues to play when stopTime is reached|media +[JDK-8222455](https://bugs.openjdk.java.net/browse/JDK-8222455)|JavaFX error loading glass.dll from cache|other +[JDK-8270839](https://bugs.openjdk.java.net/browse/JDK-8270839)|Remove deprecated implementation methods from Scene|scenegraph +[JDK-8268849](https://bugs.openjdk.java.net/browse/JDK-8268849)|Update to 612.1 version of WebKit|web +[JDK-8270479](https://bugs.openjdk.java.net/browse/JDK-8270479)|WebKit 612.1 build fails with Visual Studio 2017|web +[JDK-8272329](https://bugs.openjdk.java.net/browse/JDK-8272329)|Cherry pick GTK WebKit 2.32.3 changes|web +[JDK-8274107](https://bugs.openjdk.java.net/browse/JDK-8274107)|Cherry pick GTK WebKit 2.32.4 changes|web +[JDK-8275138](https://bugs.openjdk.java.net/browse/JDK-8275138)|WebView: UserAgent string is empty for first request|web +[JDK-8276847](https://bugs.openjdk.java.net/browse/JDK-8276847)|JSException: ReferenceError: Can't find variable: IntersectionObserver|web +[JDK-8277133](https://bugs.openjdk.java.net/browse/JDK-8277133)|Dragboard contents retrieved all over again during a DND process on WebView|web +[JDK-8277457](https://bugs.openjdk.java.net/browse/JDK-8277457)|AccessControlException: access denied ("java.net.NetPermission" "getCookieHandler")|web +[JDK-8160597](https://bugs.openjdk.java.net/browse/JDK-8160597)|IllegalArgumentException when we initiate drag on Image|window-toolkit +[JDK-8227371](https://bugs.openjdk.java.net/browse/JDK-8227371)|Drag&Drop while holding the CMD key does not work on macOS|window-toolkit +[JDK-8242544](https://bugs.openjdk.java.net/browse/JDK-8242544)|CMD+ENTER key event crashes the application when invoked on dialog|window-toolkit +[JDK-8269967](https://bugs.openjdk.java.net/browse/JDK-8269967)|JavaFX should fail fast on macOS below minimum version|window-toolkit +[JDK-8269968](https://bugs.openjdk.java.net/browse/JDK-8269968)|[REDO] Bump minimum version of macOS for x64 to 10.12|window-toolkit +[JDK-8271398](https://bugs.openjdk.java.net/browse/JDK-8271398)|GTK3 drag view image swaps red and blue color channels|window-toolkit +[JDK-8274929](https://bugs.openjdk.java.net/browse/JDK-8274929)|Crash while reading specific clipboard content|window-toolkit +[JDK-8275723](https://bugs.openjdk.java.net/browse/JDK-8275723)|Crash on macOS 12 in GlassRunnable::dealloc|window-toolkit + +## List of Security fixes + +Issue key|Summary|Subcomponent +---------|-------|------------ +JDK-8263112 (not public) | Enhance String Conclusions | graphics \ No newline at end of file From ff75917c1be200f8eeba49304eadc67842068df3 Mon Sep 17 00:00:00 2001 From: Kevin Rushforth Date: Wed, 16 Mar 2022 20:38:08 +0000 Subject: [PATCH 3/5] 8283183: Skip failing PredefinedMeshManagerTest tests Reviewed-by: jvos --- .../test/javafx/scene/shape/PredefinedMeshManagerTest.java | 3 +++ 1 file changed, 3 insertions(+) diff --git a/modules/javafx.graphics/src/test/java/test/javafx/scene/shape/PredefinedMeshManagerTest.java b/modules/javafx.graphics/src/test/java/test/javafx/scene/shape/PredefinedMeshManagerTest.java index af297ad39e8..23106f3cfff 100644 --- a/modules/javafx.graphics/src/test/java/test/javafx/scene/shape/PredefinedMeshManagerTest.java +++ b/modules/javafx.graphics/src/test/java/test/javafx/scene/shape/PredefinedMeshManagerTest.java @@ -34,6 +34,7 @@ import javafx.scene.shape.Sphere; import org.junit.Before; +import org.junit.Ignore; import org.junit.Test; import com.sun.javafx.scene.NodeHelper; @@ -78,6 +79,7 @@ public void boxCacheTest() { testShapeAddition(box1again, 2); } + @Ignore("JDK-8282449") @Test public void sphereCacheTest() { Sphere sphere1 = new Sphere(10, 1000); @@ -93,6 +95,7 @@ public void sphereCacheTest() { testShapeAddition(sphere1again, 2); } + @Ignore("JDK-8282449") @Test public void cylinderCacheTest() { Cylinder cylinder1 = new Cylinder(10, 20, 1000); From eb7fa5dd1c0911bca15576060691d884d29895a1 Mon Sep 17 00:00:00 2001 From: Peter Zhelezniakov Date: Thu, 17 Mar 2022 07:40:39 +0000 Subject: [PATCH 4/5] 8270867: Debug build of WebKit 613.1 fails on Linux Reviewed-by: kcr, jbhaskar --- .../native/Source/WTF/wtf/PlatformJava.cmake | 2 +- .../wtf/linux/MemoryPressureHandlerLinux.cpp | 140 ------------------ 2 files changed, 1 insertion(+), 141 deletions(-) delete mode 100644 modules/javafx.web/src/main/native/Source/WTF/wtf/linux/MemoryPressureHandlerLinux.cpp diff --git a/modules/javafx.web/src/main/native/Source/WTF/wtf/PlatformJava.cmake b/modules/javafx.web/src/main/native/Source/WTF/wtf/PlatformJava.cmake index 6f638ad2205..3d282ee1d0b 100644 --- a/modules/javafx.web/src/main/native/Source/WTF/wtf/PlatformJava.cmake +++ b/modules/javafx.web/src/main/native/Source/WTF/wtf/PlatformJava.cmake @@ -86,8 +86,8 @@ elseif (UNIX) generic/WorkQueueGeneric.cpp linux/CurrentProcessMemoryStatus.cpp linux/MemoryFootprintLinux.cpp - linux/MemoryPressureHandlerLinux.cpp unix/LanguageUnix.cpp + unix/MemoryPressureHandlerUnix.cpp ) list(APPEND WTF_LIBRARIES rt) elseif (WIN32) diff --git a/modules/javafx.web/src/main/native/Source/WTF/wtf/linux/MemoryPressureHandlerLinux.cpp b/modules/javafx.web/src/main/native/Source/WTF/wtf/linux/MemoryPressureHandlerLinux.cpp deleted file mode 100644 index cb5d0b6ab9e..00000000000 --- a/modules/javafx.web/src/main/native/Source/WTF/wtf/linux/MemoryPressureHandlerLinux.cpp +++ /dev/null @@ -1,140 +0,0 @@ -/* - * Copyright (C) 2011, 2012 Apple Inc. All Rights Reserved. - * Copyright (C) 2014 Raspberry Pi Foundation. All Rights Reserved. - * Copyright (C) 2018 Igalia S.L. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions - * are met: - * 1. Redistributions of source code must retain the above copyright - * notice, this list of conditions and the following disclaimer. - * 2. Redistributions in binary form must reproduce the above copyright - * notice, this list of conditions and the following disclaimer in the - * documentation and/or other materials provided with the distribution. - * - * THIS SOFTWARE IS PROVIDED BY APPLE INC. ``AS IS'' AND ANY - * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE - * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR - * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR - * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, - * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, - * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR - * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY - * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT - * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE - * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ - -#include "config.h" -#include - -#include -#include -#include -#include -#include -#include -#include - -#define LOG_CHANNEL_PREFIX Log - -namespace WTF { - -// Disable memory event reception for a minimum of s_minimumHoldOffTime -// seconds after receiving an event. Don't let events fire any sooner than -// s_holdOffMultiplier times the last cleanup processing time. Effectively -// this is 1 / s_holdOffMultiplier percent of the time. -// If after releasing the memory we don't free at least s_minimumBytesFreedToUseMinimumHoldOffTime, -// we wait longer to try again (s_maximumHoldOffTime). -// These value seems reasonable and testing verifies that it throttles frequent -// low memory events, greatly reducing CPU usage. -static const Seconds s_minimumHoldOffTime { 5_s }; -static const Seconds s_maximumHoldOffTime { 30_s }; -static const size_t s_minimumBytesFreedToUseMinimumHoldOffTime = 1 * MB; -static const unsigned s_holdOffMultiplier = 20; - -void MemoryPressureHandler::triggerMemoryPressureEvent(bool isCritical) -{ - if (!m_installed) - return; - - if (ReliefLogger::loggingEnabled()) - LOG(MemoryPressure, "Got memory pressure notification (%s)", isCritical ? "critical" : "non-critical"); - - setUnderMemoryPressure(true); - - if (isMainThread()) - respondToMemoryPressure(isCritical ? Critical::Yes : Critical::No); - else - RunLoop::main().dispatch([this, isCritical] { - respondToMemoryPressure(isCritical ? Critical::Yes : Critical::No); - }); - - if (ReliefLogger::loggingEnabled() && isUnderMemoryPressure()) - LOG(MemoryPressure, "System is no longer under memory pressure."); - - setUnderMemoryPressure(false); -} - -void MemoryPressureHandler::install() -{ - if (m_installed || m_holdOffTimer.isActive()) - return; - - m_installed = true; -} - -void MemoryPressureHandler::uninstall() -{ - if (!m_installed) - return; - - m_holdOffTimer.stop(); - - m_installed = false; -} - -void MemoryPressureHandler::holdOffTimerFired() -{ - install(); -} - -void MemoryPressureHandler::holdOff(Seconds seconds) -{ - m_holdOffTimer.startOneShot(seconds); -} - -static size_t processMemoryUsage() -{ - ProcessMemoryStatus memoryStatus; - currentProcessMemoryStatus(memoryStatus); - return (memoryStatus.resident - memoryStatus.shared); -} - -void MemoryPressureHandler::respondToMemoryPressure(Critical critical, Synchronous synchronous) -{ - uninstall(); - - MonotonicTime startTime = MonotonicTime::now(); - int64_t processMemory = processMemoryUsage(); - releaseMemory(critical, synchronous); - int64_t bytesFreed = processMemory - processMemoryUsage(); - Seconds holdOffTime = s_maximumHoldOffTime; - if (bytesFreed > 0 && static_cast(bytesFreed) >= s_minimumBytesFreedToUseMinimumHoldOffTime) - holdOffTime = (MonotonicTime::now() - startTime) * s_holdOffMultiplier; - holdOff(std::max(holdOffTime, s_minimumHoldOffTime)); -} - -void MemoryPressureHandler::platformReleaseMemory(Critical) -{ -#if HAVE(MALLOC_TRIM) - malloc_trim(0); -#endif -} - -std::optional MemoryPressureHandler::ReliefLogger::platformMemoryUsage() -{ - return MemoryUsage {processMemoryUsage(), memoryFootprint()}; -} - -} // namespace WTF From 424aebae8f28d0c75caa3281a3e5d6a7ede86a8a Mon Sep 17 00:00:00 2001 From: Alexander Matveev Date: Wed, 23 Mar 2022 20:44:03 +0000 Subject: [PATCH 5/5] 8277309: Add support for H.265/HEVC to HTTP Live Streaming Reviewed-by: kcr, arapte, jvos --- .../jfxmedia/locator/ConnectionHolder.java | 13 +- .../jfxmedia/locator/HLSConnectionHolder.java | 369 ++-- .../main/java/javafx/scene/media/package.html | 10 +- .../gst/audioparsers/gstaacparse.c | 1674 +++++++++++++++++ .../gst/audioparsers/gstaacparse.h | 105 ++ .../gst/audioparsers/parsersplugin.c | 10 + .../gst-plugins-good/gst/isomp4/qtdemux.c | 11 + .../gstreamer/plugins/av/mpegtsdemuxer.c | 4 +- .../gstreamer/plugins/av/videodecoder.c | 34 +- .../plugins/dshowwrapper/dshowwrapper.cpp | 259 ++- .../plugins/dshowwrapper/dshowwrapper.h | 7 +- .../gstreamer/plugins/javasource/javasource.c | 47 +- .../gstreamer/plugins/mfwrapper/mfwrapper.cpp | 60 +- .../gstreamer/plugins/mfwrapper/mfwrapper.h | 2 + .../progressbuffer/hlsprogressbuffer.c | 46 +- .../projects/linux/gstreamer-lite/Makefile | 1 + .../native/jfxmedia/Locator/LocatorStream.h | 5 +- .../jfxmedia/MediaManagement/MediaTypes.h | 4 +- .../jfxmedia/jni/JavaInputStreamCallbacks.cpp | 28 +- .../jfxmedia/jni/JavaInputStreamCallbacks.h | 4 +- .../gstreamer/GstAVPlaybackPipeline.cpp | 29 +- .../gstreamer/GstAVPlaybackPipeline.h | 3 +- .../gstreamer/GstAudioPlaybackPipeline.cpp | 19 + .../platform/gstreamer/GstPipelineFactory.cpp | 24 +- .../platform/gstreamer/GstPipelineFactory.h | 3 +- .../vs_project/gstreamer/gstreamer.vcxproj | 18 + .../gstreamer/gstreamer.vcxproj.filters | 56 +- .../native/vs_project/plugins/plugins.vcxproj | 3 + .../plugins/plugins.vcxproj.filters | 11 + 29 files changed, 2553 insertions(+), 306 deletions(-) create mode 100644 modules/javafx.media/src/main/native/gstreamer/gstreamer-lite/gst-plugins-good/gst/audioparsers/gstaacparse.c create mode 100644 modules/javafx.media/src/main/native/gstreamer/gstreamer-lite/gst-plugins-good/gst/audioparsers/gstaacparse.h diff --git a/modules/javafx.media/src/main/java/com/sun/media/jfxmedia/locator/ConnectionHolder.java b/modules/javafx.media/src/main/java/com/sun/media/jfxmedia/locator/ConnectionHolder.java index a345479da6c..720f347cc2c 100644 --- a/modules/javafx.media/src/main/java/com/sun/media/jfxmedia/locator/ConnectionHolder.java +++ b/modules/javafx.media/src/main/java/com/sun/media/jfxmedia/locator/ConnectionHolder.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2010, 2020, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2010, 2022, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -157,17 +157,6 @@ int property(int prop, int value) { return 0; } - /** - * Get stream size. - * Behavior can vary based on subclass implementation. - * For example HLS will load next segment and return segment size. - * - * @return - Stream size. - */ - int getStreamSize() { - return -1; - } - private static class FileConnectionHolder extends ConnectionHolder { private RandomAccessFile file = null; diff --git a/modules/javafx.media/src/main/java/com/sun/media/jfxmedia/locator/HLSConnectionHolder.java b/modules/javafx.media/src/main/java/com/sun/media/jfxmedia/locator/HLSConnectionHolder.java index bf991107d30..6673f33eb2f 100644 --- a/modules/javafx.media/src/main/java/com/sun/media/jfxmedia/locator/HLSConnectionHolder.java +++ b/modules/javafx.media/src/main/java/com/sun/media/jfxmedia/locator/HLSConnectionHolder.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2010, 2016, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2010, 2022, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -29,7 +29,11 @@ import java.io.BufferedReader; import java.io.IOException; import java.io.InputStreamReader; -import java.net.*; +import java.net.HttpURLConnection; +import java.net.MalformedURLException; +import java.net.URI; +import java.net.URISyntaxException; +import java.net.URLConnection; import java.nio.channels.Channels; import java.nio.channels.ReadableByteChannel; import java.nio.charset.Charset; @@ -43,6 +47,8 @@ final class HLSConnectionHolder extends ConnectionHolder { private URLConnection urlConnection = null; + private URLConnection headerConnection = null; + private ReadableByteChannel headerChannel = null; private PlaylistThread playlistThread = new PlaylistThread(); private VariantPlaylist variantPlaylist = null; private Playlist currentPlaylist = null; @@ -52,12 +58,18 @@ final class HLSConnectionHolder extends ConnectionHolder { private boolean isPlaylistClosed = false; private boolean isBitrateAdjustable = false; private long startTime = -1; + private boolean sendHeader = false; private static final long HLS_VALUE_FLOAT_MULTIPLIER = 1000; private static final int HLS_PROP_GET_DURATION = 1; private static final int HLS_PROP_GET_HLS_MODE = 2; private static final int HLS_PROP_GET_MIMETYPE = 3; + private static final int HLS_PROP_LOAD_SEGMENT = 4; + private static final int HLS_PROP_SEGMENT_START_TIME = 5; + private static final int HLS_VALUE_MIMETYPE_UNKNOWN = -1; private static final int HLS_VALUE_MIMETYPE_MP2T = 1; private static final int HLS_VALUE_MIMETYPE_MP3 = 2; + private static final int HLS_VALUE_MIMETYPE_FMP4 = 3; + private static final int HLS_VALUE_MIMETYPE_AAC = 4; private static final String CHARSET_UTF_8 = "UTF-8"; private static final String CHARSET_US_ASCII = "US-ASCII"; @@ -77,6 +89,19 @@ public int readNextBlock() throws IOException { startTime = System.currentTimeMillis(); } + if (headerChannel != null) { + buffer.rewind(); + if (buffer.limit() < buffer.capacity()) { + buffer.limit(buffer.capacity()); + } + int read = headerChannel.read(buffer); + if (read == -1) { + resetHeaderConnection(); + } else { + return read; + } + } + int read = super.readNextBlock(); if (isBitrateAdjustable && read == -1) { long readTime = System.currentTimeMillis() - startTime; @@ -87,26 +112,31 @@ public int readNextBlock() throws IOException { return read; } + @Override int readBlock(long position, int size) throws IOException { throw new IOException(); } + @Override boolean needBuffer() { return true; } + @Override boolean isSeekable() { return true; } + @Override boolean isRandomAccess() { return false; // Only by segments } + @Override public long seek(long position) { try { readySignal.await(); - } catch (Exception e) { + } catch (InterruptedException e) { return -1; } @@ -125,47 +155,81 @@ public void closeConnection() { int property(int prop, int value) { try { readySignal.await(); - } catch (Exception e) { + } catch (InterruptedException e) { return -1; } - if (prop == HLS_PROP_GET_DURATION) { - return (int) (currentPlaylist.getDuration() * HLS_VALUE_FLOAT_MULTIPLIER); - } else if (prop == HLS_PROP_GET_HLS_MODE) { - return 1; - } else if (prop == HLS_PROP_GET_MIMETYPE) { - return currentPlaylist.getMimeType(); + switch (prop) { + case HLS_PROP_GET_DURATION: + return (int)(currentPlaylist.getDuration() * HLS_VALUE_FLOAT_MULTIPLIER); + case HLS_PROP_GET_HLS_MODE: + return 1; + case HLS_PROP_GET_MIMETYPE: + return currentPlaylist.getMimeType(); + case HLS_PROP_LOAD_SEGMENT: + return loadNextSegment(); + case HLS_PROP_SEGMENT_START_TIME: + return (int)(currentPlaylist.getMediaFileStartTime() * HLS_VALUE_FLOAT_MULTIPLIER); + default: + return -1; } - - return -1; - } - - @Override - int getStreamSize() { - try { - readySignal.await(); - } catch (Exception e) { - return -1; - } - - return loadNextSegment(); } private void resetConnection() { super.closeConnection(); + resetHeaderConnection(); + Locator.closeConnection(urlConnection); urlConnection = null; } + private void resetHeaderConnection() { + try { + if (headerChannel != null) { + headerChannel.close(); + } + } catch (IOException ioex) {} + finally { + headerChannel = null; + } + + Locator.closeConnection(headerConnection); + headerConnection = null; + } + + // Returns -1 EOS or critical error - // Returns positive size of segment if no isssues. + // Returns positive size of segment if no issues. // Returns negative size of segment if discontinuity. private int loadNextSegment() { resetConnection(); - String mediaFile = currentPlaylist.getNextMediaFile(); + String mediaFile; + int headerLength = 0; + + if (sendHeader) { + mediaFile = currentPlaylist.getHeaderFile(); + if (mediaFile == null) { + return -1; + } + + try { + URI uri = new URI(mediaFile); + headerConnection = uri.toURL().openConnection(); + headerChannel = openHeaderChannel(); + headerLength = headerConnection.getContentLength(); + } catch (IOException | URISyntaxException e) { + return -1; + } + sendHeader = false; + } + + mediaFile = currentPlaylist.getNextMediaFile(); if (mediaFile == null) { + if (currentPlaylist.isFragmentedMP4()) { + sendHeader = true; + } return -1; } @@ -173,14 +237,14 @@ private int loadNextSegment() { URI uri = new URI(mediaFile); urlConnection = uri.toURL().openConnection(); channel = openChannel(); - } catch (Exception e) { + } catch (IOException | URISyntaxException e) { return -1; } if (currentPlaylist.isCurrentMediaFileDiscontinuity()) { - return (-1 * urlConnection.getContentLength()); + return (-1 * (urlConnection.getContentLength() + headerLength)); } else { - return urlConnection.getContentLength(); + return (urlConnection.getContentLength() + headerLength); } } @@ -188,6 +252,10 @@ private ReadableByteChannel openChannel() throws IOException { return Channels.newChannel(urlConnection.getInputStream()); } + private ReadableByteChannel openHeaderChannel() throws IOException { + return Channels.newChannel(headerConnection.getInputStream()); + } + private void adjustBitrate(long readTime) { int avgBitrate = (int)(((long) urlConnection.getContentLength() * 8 * 1000) / readTime); @@ -200,6 +268,9 @@ private void adjustBitrate(long readTime) { playlist.setForceDiscontinuity(true); currentPlaylist = playlist; + if (currentPlaylist.isFragmentedMP4()) { + sendHeader = true; + } } } @@ -216,7 +287,7 @@ private class PlaylistThread extends Thread { public static final int STATE_INIT = 0; public static final int STATE_EXIT = 1; public static final int STATE_RELOAD_PLAYLIST = 2; - private BlockingQueue stateQueue = new LinkedBlockingQueue(); + private final BlockingQueue stateQueue = new LinkedBlockingQueue<>(); private URI playlistURI = null; private Playlist reloadPlaylist = null; private final Object reloadLock = new Object(); @@ -255,7 +326,7 @@ public void run() { default: break; } - } catch (Exception e) { + } catch (InterruptedException e) { } } } @@ -274,58 +345,66 @@ private void stateInit() { return; } - PlaylistParser parser = new PlaylistParser(); - parser.load(playlistURI); + try { + PlaylistParser parser = new PlaylistParser(); + parser.load(playlistURI); - if (parser.isVariantPlaylist()) { - variantPlaylist = new VariantPlaylist(playlistURI); + if (parser.isVariantPlaylist()) { + variantPlaylist = new VariantPlaylist(playlistURI); - while (parser.hasNext()) { - variantPlaylist.addPlaylistInfo(parser.getString(), parser.getInteger()); - } - } else { - if (currentPlaylist == null) { - currentPlaylist = new Playlist(parser.isLivePlaylist(), parser.getTargetDuration()); - currentPlaylist.setPlaylistURI(playlistURI); - } - - if (currentPlaylist.setSequenceNumber(parser.getSequenceNumber())) { while (parser.hasNext()) { - currentPlaylist.addMediaFile(parser.getString(), parser.getDouble(), parser.getBoolean()); + variantPlaylist.addPlaylistInfo(parser.getString(), parser.getInteger()); + } + } else { + if (currentPlaylist == null) { + currentPlaylist = new Playlist(parser.isLivePlaylist(), parser.getTargetDuration()); + currentPlaylist.setPlaylistURI(playlistURI); + } + + if (currentPlaylist.setSequenceNumber(parser.getSequenceNumber())) { + while (parser.hasNext()) { + currentPlaylist.addMediaFile(parser.getString(), parser.getDouble(), parser.getBoolean()); + } + } + + if (variantPlaylist != null) { + variantPlaylist.addPlaylist(currentPlaylist); } } + // Update variant playlists if (variantPlaylist != null) { - variantPlaylist.addPlaylist(currentPlaylist); + while (variantPlaylist.hasNext()) { + try { + currentPlaylist = new Playlist(variantPlaylist.getPlaylistURI()); + currentPlaylist.update(null); + variantPlaylist.addPlaylist(currentPlaylist); + } catch (URISyntaxException | MalformedURLException e) { + } + } } - } - // Update variant playlists - if (variantPlaylist != null) { - while (variantPlaylist.hasNext()) { - try { - currentPlaylist = new Playlist(variantPlaylist.getPlaylistURI()); - currentPlaylist.update(null); - variantPlaylist.addPlaylist(currentPlaylist); - } catch (URISyntaxException e) { - } catch (MalformedURLException e) { - } + // Always start with first data playlist + if (variantPlaylist != null) { + currentPlaylist = variantPlaylist.getPlaylist(0); + isBitrateAdjustable = true; } - } - // Always start with first data playlist - if (variantPlaylist != null) { - currentPlaylist = variantPlaylist.getPlaylist(0); - isBitrateAdjustable = true; - } + // Start reloading live playlist + if (currentPlaylist.isLive()) { + setReloadPlaylist(currentPlaylist); + putState(STATE_RELOAD_PLAYLIST); + } - // Start reloading live playlist - if (currentPlaylist.isLive()) { - setReloadPlaylist(currentPlaylist); - putState(STATE_RELOAD_PLAYLIST); + // If we have playlist with fMP4, set flag to add header + // to first data segment and adjust index to 0 + if (currentPlaylist.isFragmentedMP4()) { + sendHeader = true; + mediaFileIndex = 0; + } + } finally { + readySignal.countDown(); } - - readySignal.countDown(); } private void stateReloadPlaylist() { @@ -358,10 +437,10 @@ private static class PlaylistParser { private int targetDuration = 0; private int sequenceNumber = 0; private int dataListIndex = -1; - private List dataListString = new ArrayList(); - private List dataListInteger = new ArrayList(); - private List dataListDouble = new ArrayList(); - private List dataListBoolean = new ArrayList(); + private List dataListString = new ArrayList<>(); + private List dataListInteger = new ArrayList<>(); + private List dataListDouble = new ArrayList<>(); + private List dataListBoolean = new ArrayList<>(); private void load(URI uri) { HttpURLConnection connection = null; @@ -416,11 +495,10 @@ private int getSequenceNumber() { private boolean hasNext() { dataListIndex++; - if (dataListString.size() > dataListIndex || dataListInteger.size() > dataListIndex || dataListDouble.size() > dataListIndex || dataListBoolean.size() > dataListIndex) { - return true; - } else { - return false; - } + return dataListString.size() > dataListIndex || + dataListInteger.size() > dataListIndex || + dataListDouble.size() > dataListIndex || + dataListBoolean.size() > dataListIndex; } private String getString() { @@ -514,14 +592,40 @@ private boolean parseLine(String line) { isEndList = true; } else if (line.startsWith("#EXT-X-DISCONTINUITY")) { // #EXT-X-DISCONTINUITY isDiscontinuity = true; + } else if (line.startsWith("#EXT-X-MAP")) { + String[] s1 = line.split(":"); + if (s1.length == 2 && s1[1].length() > 0) { + String[] s2 = s1[1].split(","); + if (s2.length > 0) { + for (int i = 0; i < s2.length; i++) { + s2[i] = s2[i].trim(); + if (s2[i].startsWith("URI")) { + String[] s3 = s2[i].split("="); + if (s3.length == 2 && s3[1].length() > 0) { + String dataFile = + s3[1].replaceAll("^\"+|\"+$", ""); + dataListString.add(dataFile); + // GStreamer expects start of stream to be + // discontinuity. + dataListBoolean.add(true); + dataListDouble.add(Double.valueOf(targetDuration)); + } + } + } + } + } } else if (isLinePlaylistURI) { isLinePlaylistURI = false; dataListString.add(line); } else if (isLineMediaFileURI) { - isLineMediaFileURI = false; - dataListString.add(line); - dataListBoolean.add(isDiscontinuity); - isDiscontinuity = false; + // We can have additional tags after #EXTINF such as + // #EXT-X-BITRATE for fMP4 playlist, so ignore them. + if (!line.startsWith("#")) { + isLineMediaFileURI = false; + dataListString.add(line); + dataListBoolean.add(isDiscontinuity); + isDiscontinuity = false; + } } return true; @@ -546,9 +650,9 @@ private static class VariantPlaylist { private URI playlistURI = null; private int infoIndex = -1; - private List playlistsLocations = new ArrayList(); - private List playlistsBitrates = new ArrayList(); - private List playlists = new ArrayList(); + private List playlistsLocations = new ArrayList<>(); + private List playlistsBitrates = new ArrayList<>(); + private List playlists = new ArrayList<>(); private String mediaFileExtension = null; // Will be set to media file extension of first playlist private VariantPlaylist(URI uri) { @@ -584,11 +688,8 @@ private Playlist getPlaylist(int index) { private boolean hasNext() { infoIndex++; - if (playlistsLocations.size() > infoIndex && playlistsBitrates.size() > infoIndex) { - return true; - } else { - return false; - } + return playlistsLocations.size() > infoIndex && + playlistsBitrates.size() > infoIndex; } private URI getPlaylistURI() throws URISyntaxException, MalformedURLException { @@ -647,16 +748,18 @@ private class Playlist { private long targetDuration = 0; private URI playlistURI = null; private final Object lock = new Object(); - private List mediaFiles = new ArrayList(); - private List mediaFilesStartTimes = new ArrayList(); - private List mediaFilesDiscontinuities = new ArrayList(); + private final List mediaFiles = new ArrayList<>(); + private final List mediaFilesStartTimes = new ArrayList<>(); + private final List mediaFilesDiscontinuities = new ArrayList<>(); private boolean needBaseURI = true; private String baseURI = null; + private double startTime = 0.0; private double duration = 0.0; private int sequenceNumber = -1; private int sequenceNumberStart = -1; private boolean sequenceNumberUpdated = false; private boolean forceDiscontinuity = false; + private int mimeType = HLS_VALUE_MIMETYPE_UNKNOWN; private Playlist(boolean isLive, int targetDuration) { this.isLive = isLive; @@ -705,6 +808,10 @@ private boolean isLive() { return isLive; } + private boolean isFragmentedMP4() { + return (getMimeType() == HLS_VALUE_MIMETYPE_FMP4); + } + private long getTargetDuration() { return targetDuration; } @@ -751,8 +858,18 @@ private void addMediaFile(String URI, double duration, boolean isDiscontinuity) liveSemaphore.release(); } } else { - mediaFilesStartTimes.add(this.duration); - this.duration += duration; + mediaFilesStartTimes.add(this.startTime); + this.startTime += duration; + + // For fragmented MP4 we should not add duration of first + // segment, since it is header without actuall data. + if (mediaFiles.size() == 1) { + if (!isFragmentedMP4()) { + this.duration += duration; + } + } else { + this.duration += duration; + } } } } @@ -782,7 +899,7 @@ private String getNextMediaFile() { synchronized (lock) { mediaFileIndex++; - if ((mediaFileIndex) < mediaFiles.size()) { + if (mediaFileIndex < mediaFiles.size()) { if (baseURI != null) { return baseURI + mediaFiles.get(mediaFileIndex); } else { @@ -794,6 +911,28 @@ private String getNextMediaFile() { } } + private String getHeaderFile() { + synchronized (lock) { + if (mediaFiles.size() > 0) { + if (baseURI != null) { + return baseURI + mediaFiles.get(0); + } else { + return mediaFiles.get(0); + } + } else { + return null; + } + } + } + + private double getMediaFileStartTime() { + if (mediaFileIndex < mediaFiles.size()) { + return mediaFilesStartTimes.get(mediaFileIndex); + } + + return 0.0; + } + private double getDuration() { return duration; } @@ -815,7 +954,12 @@ private double seek(long time) { synchronized (lock) { if (isLive) { if (time == 0) { - mediaFileIndex = -1; + if (isFragmentedMP4()) { + mediaFileIndex = 0; // Skip header at 0 index + // we will send it with first segment if needed. + } else { + mediaFileIndex = -1; + } if (isLiveWaiting) { isLiveStop = true; liveSemaphore.release(); @@ -831,12 +975,20 @@ private double seek(long time) { if (time >= mediaFilesStartTimes.get(index)) { if (index + 1 < mediaFileStartTimeSize) { if (time < mediaFilesStartTimes.get(index + 1)) { - mediaFileIndex = index - 1; // Seek will load segment and increment mediaFileIndex + if (isFragmentedMP4()) { + mediaFileIndex = index; + } else { + mediaFileIndex = index - 1; // Seek will load segment and increment mediaFileIndex + } return mediaFilesStartTimes.get(index); } } else { if ((time - targetDuration / 2000) < duration) { - mediaFileIndex = index - 1; // Seek will load segment and increment mediaFileIndex + if (isFragmentedMP4()) { + mediaFileIndex = index; + } else { + mediaFileIndex = index - 1; // Seek will load segment and increment mediaFileIndex + } return mediaFilesStartTimes.get(index); } else if (Double.compare(time - targetDuration / 2000, duration) == 0) { return duration; @@ -852,16 +1004,23 @@ private double seek(long time) { private int getMimeType() { synchronized (lock) { - if (mediaFiles.size() > 0) { - if (stripParameters(mediaFiles.get(0)).endsWith(".ts")) { - return HLS_VALUE_MIMETYPE_MP2T; - } else if (stripParameters(mediaFiles.get(0)).endsWith(".mp3")) { - return HLS_VALUE_MIMETYPE_MP3; + if (mimeType == HLS_VALUE_MIMETYPE_UNKNOWN) { + if (mediaFiles.size() > 0) { + if (stripParameters(mediaFiles.get(0)).endsWith(".ts")) { + mimeType = HLS_VALUE_MIMETYPE_MP2T; + } else if (stripParameters(mediaFiles.get(0)).endsWith(".mp3")) { + mimeType = HLS_VALUE_MIMETYPE_MP3; + } else if (stripParameters(mediaFiles.get(0)).endsWith(".mp4") + || stripParameters(mediaFiles.get(0)).endsWith(".m4s")) { + mimeType = HLS_VALUE_MIMETYPE_FMP4; + } else if (stripParameters(mediaFiles.get(0)).endsWith(".aac")) { + mimeType = HLS_VALUE_MIMETYPE_AAC; + } } } } - return -1; + return mimeType; } private String getMediaFileExtension() { diff --git a/modules/javafx.media/src/main/java/javafx/scene/media/package.html b/modules/javafx.media/src/main/java/javafx/scene/media/package.html index 4442d0cb06f..d5438db2d67 100644 --- a/modules/javafx.media/src/main/java/javafx/scene/media/package.html +++ b/modules/javafx.media/src/main/java/javafx/scene/media/package.html @@ -2,7 +2,7 @@