Initial commit for VideoView2Impl
Test: build
Change-Id: I2f5e7f85b5a7358c707cf3897f354ead42980956
diff --git a/packages/MediaComponents/Android.mk b/packages/MediaComponents/Android.mk
index 6c408c3..bbd2afa 100644
--- a/packages/MediaComponents/Android.mk
+++ b/packages/MediaComponents/Android.mk
@@ -32,6 +32,8 @@
LOCAL_MULTILIB := first
+LOCAL_JAVA_LIBRARIES += android-support-annotations
+
# Embed native libraries in package, rather than installing to /system/lib*.
# TODO: Find a right way to include libs in the apk. b/72066556
LOCAL_MODULE_TAGS := samples
diff --git a/packages/MediaComponents/res/drawable-hdpi/ic_media_cc_disabled.png b/packages/MediaComponents/res/drawable-hdpi/ic_media_cc_disabled.png
new file mode 100644
index 0000000..0354f61
--- /dev/null
+++ b/packages/MediaComponents/res/drawable-hdpi/ic_media_cc_disabled.png
Binary files differ
diff --git a/packages/MediaComponents/res/drawable-hdpi/ic_media_cc_enabled.png b/packages/MediaComponents/res/drawable-hdpi/ic_media_cc_enabled.png
new file mode 100644
index 0000000..5f8febe
--- /dev/null
+++ b/packages/MediaComponents/res/drawable-hdpi/ic_media_cc_enabled.png
Binary files differ
diff --git a/packages/MediaComponents/res/drawable-mdpi/ic_media_cc_disabled.png b/packages/MediaComponents/res/drawable-mdpi/ic_media_cc_disabled.png
new file mode 100644
index 0000000..0354f61
--- /dev/null
+++ b/packages/MediaComponents/res/drawable-mdpi/ic_media_cc_disabled.png
Binary files differ
diff --git a/packages/MediaComponents/res/drawable-mdpi/ic_media_cc_enabled.png b/packages/MediaComponents/res/drawable-mdpi/ic_media_cc_enabled.png
new file mode 100644
index 0000000..5f8febe
--- /dev/null
+++ b/packages/MediaComponents/res/drawable-mdpi/ic_media_cc_enabled.png
Binary files differ
diff --git a/packages/MediaComponents/res/drawable/ic_arrow_back.xml b/packages/MediaComponents/res/drawable/ic_arrow_back.xml
new file mode 100644
index 0000000..5aba8c6
--- /dev/null
+++ b/packages/MediaComponents/res/drawable/ic_arrow_back.xml
@@ -0,0 +1,9 @@
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="40dp"
+ android:height="40dp"
+ android:viewportWidth="24.0"
+ android:viewportHeight="24.0">
+ <path
+ android:pathData="M20,11H7.83l5.59,-5.59L12,4l-8,8 8,8 1.41,-1.41L7.83,13H20v-2z"
+ android:fillColor="#FFFFFF"/>
+</vector>
diff --git a/packages/MediaComponents/res/drawable/ic_cast.xml b/packages/MediaComponents/res/drawable/ic_cast.xml
new file mode 100644
index 0000000..ac22a4b
--- /dev/null
+++ b/packages/MediaComponents/res/drawable/ic_cast.xml
@@ -0,0 +1,9 @@
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="24dp"
+ android:height="24dp"
+ android:viewportWidth="24.0"
+ android:viewportHeight="24.0">
+ <path
+ android:pathData="M21,3L3,3c-1.1,0 -2,0.9 -2,2v3h2L3,5h18v14h-7v2h7c1.1,0 2,-0.9 2,-2L23,5c0,-1.1 -0.9,-2 -2,-2zM1,18v3h3c0,-1.66 -1.34,-3 -3,-3zM1,14v2c2.76,0 5,2.24 5,5h2c0,-3.87 -3.13,-7 -7,-7zM1,10v2c4.97,0 9,4.03 9,9h2c0,-6.08 -4.93,-11 -11,-11z"
+ android:fillColor="#FFFFFF"/>
+</vector>
diff --git a/packages/MediaComponents/res/drawable/ic_chevron_left.xml b/packages/MediaComponents/res/drawable/ic_chevron_left.xml
new file mode 100644
index 0000000..8336d17
--- /dev/null
+++ b/packages/MediaComponents/res/drawable/ic_chevron_left.xml
@@ -0,0 +1,9 @@
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="24dp"
+ android:height="24dp"
+ android:viewportWidth="24.0"
+ android:viewportHeight="24.0">
+ <path
+ android:pathData="M15.41,7.41L14,6l-6,6 6,6 1.41,-1.41L10.83,12z"
+ android:fillColor="#FFFFFF"/>
+</vector>
diff --git a/packages/MediaComponents/res/drawable/ic_chevron_right.xml b/packages/MediaComponents/res/drawable/ic_chevron_right.xml
new file mode 100644
index 0000000..fb2ce09
--- /dev/null
+++ b/packages/MediaComponents/res/drawable/ic_chevron_right.xml
@@ -0,0 +1,9 @@
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="24dp"
+ android:height="24dp"
+ android:viewportWidth="24.0"
+ android:viewportHeight="24.0">
+ <path
+ android:pathData="M10,6L8.59,7.41 13.17,12l-4.58,4.59L10,18l6,-6z"
+ android:fillColor="#FFFFFF"/>
+</vector>
diff --git a/packages/MediaComponents/res/drawable/ic_forward_30.xml b/packages/MediaComponents/res/drawable/ic_forward_30.xml
new file mode 100644
index 0000000..7efdf16
--- /dev/null
+++ b/packages/MediaComponents/res/drawable/ic_forward_30.xml
@@ -0,0 +1,12 @@
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="40dp"
+ android:height="40dp"
+ android:viewportWidth="24.0"
+ android:viewportHeight="24.0">
+ <group>
+ <clip-path android:pathData="M24,24H0V0h24v24z M 0,0" />
+ <path
+ android:pathData="M9.6 13.5h.4c.2 0 .4,-.1.5,-.2s.2,-.2.2,-.4v-.2s-.1,-.1,-.1,-.2,-.1,-.1,-.2,-.1h-.5s-.1.1,-.2.1,-.1.1,-.1.2v.2h-1c0,-.2 0,-.3.1,-.5s.2,-.3.3,-.4.3,-.2.4,-.2.4,-.1.5,-.1c.2 0 .4 0 .6.1s.3.1.5.2.2.2.3.4.1.3.1.5v.3s-.1.2,-.1.3,-.1.2,-.2.2,-.2.1,-.3.2c.2.1.4.2.5.4s.2.4.2.6c0 .2 0 .4,-.1.5s-.2.3,-.3.4,-.3.2,-.5.2,-.4.1,-.6.1c-.2 0,-.4 0,-.5,-.1s-.3,-.1,-.5,-.2,-.2,-.2,-.3,-.4,-.1,-.4,-.1,-.6h.8v.2s.1.1.1.2.1.1.2.1h.5s.1,-.1.2,-.1.1,-.1.1,-.2v-.5s-.1,-.1,-.1,-.2,-.1,-.1,-.2,-.1h-.6v-.7zm5.7.7c0 .3 0 .6,-.1.8l-.3.6s-.3.3,-.5.3,-.4.1,-.6.1,-.4 0,-.6,-.1,-.3,-.2,-.5,-.3,-.2,-.3,-.3,-.6,-.1,-.5,-.1,-.8v-.7c0,-.3 0,-.6.1,-.8l.3,-.6s.3,-.3.5,-.3.4,-.1.6,-.1.4 0 .6.1.3.2.5.3.2.3.3.6.1.5.1.8v.7zm-.9,-.8v-.5s-.1,-.2,-.1,-.3,-.1,-.1,-.2,-.2,-.2,-.1,-.3,-.1,-.2 0,-.3.1l-.2.2s-.1.2,-.1.3v2s.1.2.1.3.1.1.2.2.2.1.3.1.2 0 .3,-.1l.2,-.2s.1,-.2.1,-.3v-1.5zM4 13c0 4.4 3.6 8 8 8s8,-3.6 8,-8h-2c0 3.3,-2.7 6,-6 6s-6,-2.7,-6,-6 2.7,-6 6,-6v4l5,-5,-5,-5v4c-4.4 0,-8 3.6,-8 8z"
+ android:fillColor="#FFFFFF"/>
+ </group>
+</vector>
diff --git a/packages/MediaComponents/res/drawable/ic_fullscreen.xml b/packages/MediaComponents/res/drawable/ic_fullscreen.xml
new file mode 100644
index 0000000..4b4f6bc
--- /dev/null
+++ b/packages/MediaComponents/res/drawable/ic_fullscreen.xml
@@ -0,0 +1,9 @@
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="24dp"
+ android:height="24dp"
+ android:viewportWidth="24.0"
+ android:viewportHeight="24.0">
+ <path
+ android:pathData="M7,14L5,14v5h5v-2L7,17v-3zM5,10h2L7,7h3L10,5L5,5v5zM17,17h-3v2h5v-5h-2v3zM14,5v2h3v3h2L19,5h-5z"
+ android:fillColor="#FFFFFF"/>
+</vector>
diff --git a/packages/MediaComponents/res/drawable/ic_fullscreen_exit.xml b/packages/MediaComponents/res/drawable/ic_fullscreen_exit.xml
new file mode 100644
index 0000000..bc204e2
--- /dev/null
+++ b/packages/MediaComponents/res/drawable/ic_fullscreen_exit.xml
@@ -0,0 +1,9 @@
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="24dp"
+ android:height="24dp"
+ android:viewportWidth="24.0"
+ android:viewportHeight="24.0">
+ <path
+ android:pathData="M5,16h3v3h2v-5L5,14v2zM8,8L5,8v2h5L10,5L8,5v3zM14,19h2v-3h3v-2h-5v5zM16,8L16,5h-2v5h5L19,8h-3z"
+ android:fillColor="#FFFFFF"/>
+</vector>
diff --git a/packages/MediaComponents/res/drawable/ic_pause_circle_filled.xml b/packages/MediaComponents/res/drawable/ic_pause_circle_filled.xml
new file mode 100644
index 0000000..73be228
--- /dev/null
+++ b/packages/MediaComponents/res/drawable/ic_pause_circle_filled.xml
@@ -0,0 +1,9 @@
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="40dp"
+ android:height="40dp"
+ android:viewportWidth="24.0"
+ android:viewportHeight="24.0">
+ <path
+ android:pathData="M12,2C6.48,2 2,6.48 2,12s4.48,10 10,10 10,-4.48 10,-10S17.52,2 12,2zM11,16L9,16L9,8h2v8zM15,16h-2L13,8h2v8z"
+ android:fillColor="#FFFFFF"/>
+</vector>
diff --git a/packages/MediaComponents/res/drawable/ic_play_circle_filled.xml b/packages/MediaComponents/res/drawable/ic_play_circle_filled.xml
new file mode 100644
index 0000000..9d39def
--- /dev/null
+++ b/packages/MediaComponents/res/drawable/ic_play_circle_filled.xml
@@ -0,0 +1,9 @@
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="40dp"
+ android:height="40dp"
+ android:viewportWidth="24.0"
+ android:viewportHeight="24.0">
+ <path
+ android:pathData="M12,2C6.48,2 2,6.48 2,12s4.48,10 10,10 10,-4.48 10,-10S17.52,2 12,2zM10,16.5v-9l6,4.5 -6,4.5z"
+ android:fillColor="#FFFFFF"/>
+</vector>
diff --git a/packages/MediaComponents/res/drawable/ic_rewind_10.xml b/packages/MediaComponents/res/drawable/ic_rewind_10.xml
new file mode 100644
index 0000000..ae586b4
--- /dev/null
+++ b/packages/MediaComponents/res/drawable/ic_rewind_10.xml
@@ -0,0 +1,12 @@
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="40dp"
+ android:height="40dp"
+ android:viewportWidth="24.0"
+ android:viewportHeight="24.0">
+ <group>
+ <clip-path android:pathData="M0,0h24v24H0V0z M 0,0" />
+ <path
+ android:pathData="M12 5V1L7 6l5 5V7c3.3 0 6 2.7 6 6s-2.7 6,-6 6,-6,-2.7,-6,-6H4c0 4.4 3.6 8 8 8s8,-3.6 8,-8,-3.6,-8,-8,-8zm-1.1 11H10v-3.3L9 13v-.7l1.8,-.6h.1V16zm4.3,-1.8c0 .3 0 .6,-.1.8l-.3.6s-.3.3,-.5.3,-.4.1,-.6.1,-.4 0,-.6,-.1,-.3,-.2,-.5,-.3,-.2,-.3,-.3,-.6,-.1,-.5,-.1,-.8v-.7c0,-.3 0,-.6.1,-.8l.3,-.6s.3,-.3.5,-.3.4,-.1.6,-.1.4 0 .6.1c.2.1.3.2.5.3s.2.3.3.6.1.5.1.8v.7zm-.9,-.8v-.5s-.1,-.2,-.1,-.3,-.1,-.1,-.2,-.2,-.2,-.1,-.3,-.1,-.2 0,-.3.1l-.2.2s-.1.2,-.1.3v2s.1.2.1.3.1.1.2.2.2.1.3.1.2 0 .3,-.1l.2,-.2s.1,-.2.1,-.3v-1.5z"
+ android:fillColor="#FFFFFF"/>
+ </group>
+</vector>
diff --git a/packages/MediaComponents/res/drawable/ic_skip_next.xml b/packages/MediaComponents/res/drawable/ic_skip_next.xml
new file mode 100644
index 0000000..b1f2812
--- /dev/null
+++ b/packages/MediaComponents/res/drawable/ic_skip_next.xml
@@ -0,0 +1,9 @@
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="40dp"
+ android:height="40dp"
+ android:viewportWidth="24.0"
+ android:viewportHeight="24.0">
+ <path
+ android:pathData="M6,18l8.5,-6L6,6v12zM16,6v12h2V6h-2z"
+ android:fillColor="#FFFFFF"/>
+</vector>
diff --git a/packages/MediaComponents/res/drawable/ic_skip_previous.xml b/packages/MediaComponents/res/drawable/ic_skip_previous.xml
new file mode 100644
index 0000000..81da314
--- /dev/null
+++ b/packages/MediaComponents/res/drawable/ic_skip_previous.xml
@@ -0,0 +1,9 @@
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="40dp"
+ android:height="40dp"
+ android:viewportWidth="24.0"
+ android:viewportHeight="24.0">
+ <path
+ android:pathData="M6,6h2v12L6,18zM9.5,12l8.5,6L18,6z"
+ android:fillColor="#FFFFFF"/>
+</vector>
diff --git a/packages/MediaComponents/res/layout/media_controller.xml b/packages/MediaComponents/res/layout/media_controller.xml
new file mode 100644
index 0000000..ff4f12a
--- /dev/null
+++ b/packages/MediaComponents/res/layout/media_controller.xml
@@ -0,0 +1,136 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2017 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.
+-->
+
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:background="#55000000"
+ android:orientation="vertical"
+ android:layoutDirection="ltr">
+
+ <RelativeLayout
+ android:id="@+id/title_bar"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content">
+
+ <RadioButton
+ android:id="@+id/back"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_alignParentStart="true"
+ android:layout_centerVertical="true"
+ android:checked="true"
+ android:visibility="gone"/>
+
+ <TextView
+ android:id="@+id/title_text"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_toRightOf="@id/back"
+ android:layout_centerVertical="true"
+ android:layout_marginLeft="15dp"
+ android:paddingTop="4dp"
+ android:paddingStart="4dp"
+ android:paddingEnd="4dp"
+ android:textSize="20sp"
+ android:text="North by Northwest"
+ android:textColor="#FFFFFFFF" />
+
+ <ImageButton
+ android:id="@+id/cast"
+ android:layout_alignParentEnd="true"
+ android:layout_centerVertical="true"
+ style="@style/TitleBarButton.MediaRouteButton"/>
+
+ </RelativeLayout>
+
+ <LinearLayout
+ android:layout_width="match_parent"
+ android:layout_height="0dp"
+ android:layout_weight="1"
+ android:gravity="center"
+ android:paddingTop="4dp"
+ android:orientation="horizontal">
+
+ <ImageButton android:id="@+id/prev" style="@style/TransportControlsButton.Previous" />
+ <ImageButton android:id="@+id/rew" style="@style/TransportControlsButton.Rew" />
+ <ImageButton android:id="@+id/pause" style="@style/TransportControlsButton.Pause" />
+ <ImageButton android:id="@+id/ffwd" style="@style/TransportControlsButton.Ffwd" />
+ <ImageButton android:id="@+id/next" style="@style/TransportControlsButton.Next" />
+
+ </LinearLayout>
+
+ <SeekBar
+ android:id="@+id/mediacontroller_progress"
+ android:layout_width="match_parent"
+ android:layout_height="32dp"
+ android:padding="0dp"
+ android:progressTint="#FFFFFFFF"
+ android:thumbTint="#FFFFFFFF"/>
+
+ <RelativeLayout
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:paddingLeft="15dp"
+ android:paddingRight="15dp"
+ android:orientation="horizontal">
+
+ <TextView
+ android:id="@+id/time_current"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_centerVertical="true"
+ android:layout_alignParentStart="true"
+ android:paddingEnd="4dp"
+ android:paddingStart="4dp"
+ android:textSize="14sp"
+ android:textStyle="bold"
+ android:textColor="#FFFFFF" />
+
+ <TextView
+ android:id="@+id/time"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_centerVertical="true"
+ android:layout_toRightOf="@id/time_current"
+ android:paddingStart="4dp"
+ android:paddingEnd="4dp"
+ android:textSize="14sp"
+ android:textStyle="bold"
+ android:textColor="#BBBBBB" />
+
+ <ImageButton
+ android:id="@+id/overflow"
+ android:layout_alignParentEnd="true"
+ android:layout_centerVertical="true"
+ style="@style/BottomBarButton.Overflow"/>
+
+ <ImageButton
+ android:id="@+id/fullscreen"
+ android:layout_toLeftOf="@id/overflow"
+ android:layout_centerVertical="true"
+ style="@style/BottomBarButton.FullScreen"/>
+
+ <ImageButton
+ android:id="@+id/cc"
+ android:scaleType="fitCenter"
+ android:layout_toLeftOf="@id/fullscreen"
+ android:layout_centerVertical="true"
+ style="@style/BottomBarButton.CC" />
+
+ </RelativeLayout>
+
+</LinearLayout>
diff --git a/packages/MediaComponents/res/values/attrs.xml b/packages/MediaComponents/res/values/attrs.xml
index e37285b..6175b11 100644
--- a/packages/MediaComponents/res/values/attrs.xml
+++ b/packages/MediaComponents/res/values/attrs.xml
@@ -41,4 +41,5 @@
<attr name="mediaRouteControlPanelThemeOverlay" format="reference" />
<attr name="mediaRouteTheme" format="reference" />
+ <attr name="enableControlView" format="boolean" />
</resources>
diff --git a/packages/MediaComponents/res/values/strings.xml b/packages/MediaComponents/res/values/strings.xml
index ca9dd6b..e93269c 100644
--- a/packages/MediaComponents/res/values/strings.xml
+++ b/packages/MediaComponents/res/values/strings.xml
@@ -81,4 +81,14 @@
<!-- Placeholder text indicating that the user is currently casting screen. [CHAR LIMIT=50] -->
<string name="mr_controller_casting_screen">Casting screen</string>
+
+ <string name="lockscreen_pause_button_content_description">Pause</string>
+ <string name="lockscreen_play_button_content_description">Play</string>
+
+ <!-- Text for error alert when a video container is not valid for progressive download/playback. -->
+ <string name="VideoView2_error_text_invalid_progressive_playback">This video isn\'t valid for streaming to this device.</string>
+ <!-- Text for error alert when a video cannot be played. It can be used by any app. -->
+ <string name="VideoView2_error_text_unknown">Can\'t play this video.</string>
+ <!-- Button to close error alert when a video cannot be played. -->
+ <string name="VideoView2_error_button">OK</string>
</resources>
diff --git a/packages/MediaComponents/res/values/style.xml b/packages/MediaComponents/res/values/style.xml
new file mode 100644
index 0000000..d31b41d
--- /dev/null
+++ b/packages/MediaComponents/res/values/style.xml
@@ -0,0 +1,62 @@
+<?xml version="1.0" encoding="utf-8"?>
+<resources>
+ <style name="TransportControlsButton">
+ <item name="android:background">@null</item>
+ <item name="android:layout_width">70dp</item>
+ <item name="android:layout_height">40dp</item>
+ </style>
+
+ <style name="TransportControlsButton.Previous">
+ <item name="android:src">@drawable/ic_skip_previous</item>
+ </style>
+
+ <style name="TransportControlsButton.Next">
+ <item name="android:src">@drawable/ic_skip_next</item>
+ </style>
+
+ <style name="TransportControlsButton.Pause">
+ <item name="android:src">@drawable/ic_pause_circle_filled</item>
+ </style>
+
+ <style name="TransportControlsButton.Ffwd">
+ <item name="android:src">@drawable/ic_forward_30</item>
+ </style>
+
+ <style name="TransportControlsButton.Rew">
+ <item name="android:src">@drawable/ic_rewind_10</item>
+ </style>
+
+
+ <style name="TitleBarButton">
+ <item name="android:background">@null</item>
+ <item name="android:layout_width">36dp</item>
+ <item name="android:layout_height">36dp</item>
+ <item name="android:layout_margin">10dp</item>
+ </style>
+
+ <style name="TitleBarButton.MediaRouteButton">
+ <item name="android:src">@drawable/ic_cast</item>
+ </style>
+
+
+ <style name="BottomBarButton">
+ <item name="android:background">@null</item>
+ <item name="android:layout_width">24dp</item>
+ <item name="android:layout_height">24dp</item>
+ <item name="android:layout_margin">10dp</item>
+ </style>
+
+ <style name="BottomBarButton.CC">
+ <item name="android:src">@drawable/ic_media_cc_disabled</item>
+ </style>
+
+ <style name="BottomBarButton.FullScreen">
+ <item name="android:src">@drawable/ic_fullscreen</item>
+ </style>
+
+ <style name="BottomBarButton.Overflow">
+ <item name="android:src">@drawable/ic_chevron_right</item>
+ </style>
+
+</resources>
+
diff --git a/packages/MediaComponents/res/values/symbols.xml b/packages/MediaComponents/res/values/symbols.xml
new file mode 100644
index 0000000..ee0e8c6
--- /dev/null
+++ b/packages/MediaComponents/res/values/symbols.xml
@@ -0,0 +1,28 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/* Copyright 2017, 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.
+*/
+-->
+<resources>
+ <!--java-symbol type="id" name="cc" />
+ <java-symbol type="id" name="ffwd" />
+ <java-symbol type="id" name="mediacontroller_progress" />
+ <java-symbol type="id" name="next" />
+ <java-symbol type="id" name="pause" />
+ <java-symbol type="id" name="prev" />
+ <java-symbol type="id" name="rew" />
+ <java-symbol type="id" name="time" />
+ <java-symbol type="id" name="time_current" /-->
+</resources>
\ No newline at end of file
diff --git a/packages/MediaComponents/src/com/android/media/update/ApiFactory.java b/packages/MediaComponents/src/com/android/media/update/ApiFactory.java
index f015b86..633a342 100644
--- a/packages/MediaComponents/src/com/android/media/update/ApiFactory.java
+++ b/packages/MediaComponents/src/com/android/media/update/ApiFactory.java
@@ -18,14 +18,16 @@
import android.content.res.Resources;
import android.content.res.Resources.Theme;
-import android.media.update.MediaController2Provider;
+import android.media.update.MediaControlView2Provider;
import android.media.update.VideoView2Provider;
import android.media.update.StaticProvider;
import android.media.update.ViewProvider;
-import android.widget.MediaController2;
+import android.support.annotation.Nullable;
+import android.util.AttributeSet;
+import android.widget.MediaControlView2;
import android.widget.VideoView2;
-import com.android.widget.MediaController2Impl;
+import com.android.widget.MediaControlView2Impl;
import com.android.widget.VideoView2Impl;
public class ApiFactory implements StaticProvider {
@@ -36,13 +38,15 @@
}
@Override
- public MediaController2Provider createMediaController2(
- MediaController2 instance, ViewProvider superProvider) {
- return new MediaController2Impl(instance, superProvider);
+ public MediaControlView2Provider createMediaControlView2(
+ MediaControlView2 instance, ViewProvider superProvider) {
+ return new MediaControlView2Impl(instance, superProvider);
}
@Override
- public VideoView2Provider createVideoView2(VideoView2 instance, ViewProvider superProvider) {
- return new VideoView2Impl(instance, superProvider);
+ public VideoView2Provider createVideoView2(
+ VideoView2 instance, ViewProvider superProvider,
+ @Nullable AttributeSet attrs, int defStyleAttr, int defStyleRes) {
+ return new VideoView2Impl(instance, superProvider, attrs, defStyleAttr, defStyleRes);
}
}
diff --git a/packages/MediaComponents/src/com/android/media/update/ApiHelper.java b/packages/MediaComponents/src/com/android/media/update/ApiHelper.java
index 07edb03..26f858c 100644
--- a/packages/MediaComponents/src/com/android/media/update/ApiHelper.java
+++ b/packages/MediaComponents/src/com/android/media/update/ApiHelper.java
@@ -39,11 +39,11 @@
mLibTheme = libTheme;
}
- public Resources getLibResources() {
- return mLibResources;
+ public static Resources getLibResources() {
+ return sInstance.mLibResources;
}
- public Resources.Theme getLibTheme() {
- return mLibTheme;
+ public static Resources.Theme getLibTheme() {
+ return sInstance.mLibTheme;
}
}
diff --git a/packages/MediaComponents/src/com/android/widget/MediaController2Impl.java b/packages/MediaComponents/src/com/android/widget/MediaControlView2Impl.java
similarity index 72%
rename from packages/MediaComponents/src/com/android/widget/MediaController2Impl.java
rename to packages/MediaComponents/src/com/android/widget/MediaControlView2Impl.java
index d322a20..8053245e 100644
--- a/packages/MediaComponents/src/com/android/widget/MediaController2Impl.java
+++ b/packages/MediaComponents/src/com/android/widget/MediaControlView2Impl.java
@@ -16,21 +16,22 @@
package com.android.widget;
-import android.graphics.Canvas;
import android.media.session.MediaController;
-import android.media.update.MediaController2Provider;
+import android.media.update.MediaControlView2Provider;
import android.media.update.ViewProvider;
import android.view.KeyEvent;
import android.view.MotionEvent;
import android.view.View;
-import android.view.View.OnClickListener;
-import android.widget.MediaController2;
+import android.widget.MediaControlView2;
-public class MediaController2Impl implements MediaController2Provider {
- private final MediaController2 mInstance;
+public class MediaControlView2Impl implements MediaControlView2Provider {
+ private final MediaControlView2 mInstance;
private final ViewProvider mSuperProvider;
- public MediaController2Impl(MediaController2 instance, ViewProvider superProvider) {
+ static final String ACTION_SHOW_SUBTITLE = "showSubtitle";
+ static final String ACTION_HIDE_SUBTITLE = "hideSubtitle";
+
+ public MediaControlView2Impl(MediaControlView2 instance, ViewProvider superProvider) {
mInstance = instance;
mSuperProvider = superProvider;
@@ -43,11 +44,6 @@
}
@Override
- public void setAnchorView_impl(View view) {
- // TODO: Implement
- }
-
- @Override
public void show_impl() {
// TODO: Implement
}
@@ -69,11 +65,6 @@
}
@Override
- public void setPrevNextListeners_impl(OnClickListener next, OnClickListener prev) {
- // TODO: Implement
- }
-
- @Override
public void showCCButton_impl() {
// TODO: Implement
}
@@ -125,33 +116,9 @@
}
@Override
- public void onAttachedToWindow_impl() {
- mSuperProvider.onAttachedToWindow_impl();
- // TODO: Implement
- }
-
- @Override
- public void onDetachedFromWindow_impl() {
- mSuperProvider.onDetachedFromWindow_impl();
- // TODO: Implement
- }
-
- @Override
- public void onLayout_impl(boolean changed, int left, int top, int right, int bottom) {
- mSuperProvider.onLayout_impl(changed, left, top, right, bottom);
- // TODO: Implement
- }
-
- @Override
- public void draw_impl(Canvas canvas) {
- mSuperProvider.draw_impl(canvas);
- // TODO: Implement
- }
-
- @Override
public CharSequence getAccessibilityClassName_impl() {
// TODO: Implement
- return MediaController2.class.getName();
+ return MediaControlView2.class.getName();
}
@Override
diff --git a/packages/MediaComponents/src/com/android/widget/SubtitleView.java b/packages/MediaComponents/src/com/android/widget/SubtitleView.java
new file mode 100644
index 0000000..9071967
--- /dev/null
+++ b/packages/MediaComponents/src/com/android/widget/SubtitleView.java
@@ -0,0 +1,142 @@
+/*
+ * Copyright 2018 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.android.widget;
+
+import android.content.Context;
+import android.graphics.Canvas;
+import android.media.SubtitleController.Anchor;
+import android.media.SubtitleTrack.RenderingWidget;
+import android.os.Looper;
+import android.support.annotation.Nullable;
+import android.util.AttributeSet;
+import android.widget.FrameLayout;
+
+class SubtitleView extends FrameLayout implements Anchor {
+ private static final String TAG = "SubtitleView";
+
+ private RenderingWidget mSubtitleWidget;
+ private RenderingWidget.OnChangedListener mSubtitlesChangedListener;
+
+ public SubtitleView(Context context) {
+ this(context, null);
+ }
+
+ public SubtitleView(Context context, @Nullable AttributeSet attrs) {
+ this(context, attrs, 0);
+ }
+
+ public SubtitleView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
+ this(context, attrs, defStyleAttr, 0);
+ }
+
+ public SubtitleView(
+ Context context, @Nullable AttributeSet attrs, int defStyleAttr, int defStyleRes) {
+ super(context, attrs, defStyleAttr, defStyleRes);
+ }
+
+ @Override
+ public void setSubtitleWidget(RenderingWidget subtitleWidget) {
+ if (mSubtitleWidget == subtitleWidget) {
+ return;
+ }
+
+ final boolean attachedToWindow = isAttachedToWindow();
+ if (mSubtitleWidget != null) {
+ if (attachedToWindow) {
+ mSubtitleWidget.onDetachedFromWindow();
+ }
+
+ mSubtitleWidget.setOnChangedListener(null);
+ }
+ mSubtitleWidget = subtitleWidget;
+
+ if (subtitleWidget != null) {
+ if (mSubtitlesChangedListener == null) {
+ mSubtitlesChangedListener = new RenderingWidget.OnChangedListener() {
+ @Override
+ public void onChanged(RenderingWidget renderingWidget) {
+ invalidate();
+ }
+ };
+ }
+
+ setWillNotDraw(false);
+ subtitleWidget.setOnChangedListener(mSubtitlesChangedListener);
+
+ if (attachedToWindow) {
+ subtitleWidget.onAttachedToWindow();
+ requestLayout();
+ }
+ } else {
+ setWillNotDraw(true);
+ }
+
+ invalidate();
+ }
+
+ @Override
+ public Looper getSubtitleLooper() {
+ return Looper.getMainLooper();
+ }
+
+ @Override
+ public void onAttachedToWindow() {
+ super.onAttachedToWindow();
+
+ if (mSubtitleWidget != null) {
+ mSubtitleWidget.onAttachedToWindow();
+ }
+ }
+
+ @Override
+ public void onDetachedFromWindow() {
+ super.onDetachedFromWindow();
+
+ if (mSubtitleWidget != null) {
+ mSubtitleWidget.onDetachedFromWindow();
+ }
+ }
+
+ @Override
+ public void onLayout(boolean changed, int left, int top, int right, int bottom) {
+ super.onLayout(changed, left, top, right, bottom);
+
+ if (mSubtitleWidget != null) {
+ final int width = getWidth() - getPaddingLeft() - getPaddingRight();
+ final int height = getHeight() - getPaddingTop() - getPaddingBottom();
+
+ mSubtitleWidget.setSize(width, height);
+ }
+ }
+
+ @Override
+ public void draw(Canvas canvas) {
+ super.draw(canvas);
+
+ if (mSubtitleWidget != null) {
+ final int saveCount = canvas.save();
+ canvas.translate(getPaddingLeft(), getPaddingTop());
+ mSubtitleWidget.draw(canvas);
+ canvas.restoreToCount(saveCount);
+ }
+ }
+
+ @Override
+ public CharSequence getAccessibilityClassName() {
+ return SubtitleView.class.getName();
+ }
+}
diff --git a/packages/MediaComponents/src/com/android/widget/VideoSurfaceView.java b/packages/MediaComponents/src/com/android/widget/VideoSurfaceView.java
new file mode 100644
index 0000000..800be8e
--- /dev/null
+++ b/packages/MediaComponents/src/com/android/widget/VideoSurfaceView.java
@@ -0,0 +1,233 @@
+/*
+ * Copyright 2018 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.android.widget;
+
+import android.content.Context;
+import android.graphics.Rect;
+import android.media.MediaPlayer;
+import android.support.annotation.NonNull;
+import android.util.AttributeSet;
+import android.util.Log;
+import android.view.SurfaceHolder;
+import android.view.SurfaceView;
+import android.view.View;
+
+import static android.widget.VideoView2.VIEW_TYPE_SURFACEVIEW;
+
+class VideoSurfaceView extends SurfaceView implements VideoViewInterface, SurfaceHolder.Callback {
+ private static final String TAG = "VideoSurfaceView";
+ private static final boolean DEBUG = true; // STOPSHIP: Log.isLoggable(TAG, Log.DEBUG);
+ private SurfaceHolder mSurfaceHolder = null;
+ private SurfaceListener mSurfaceListener = null;
+ private MediaPlayer mMediaPlayer;
+ // A flag to indicate taking over other view should be proceed.
+ private boolean mIsTakingOverOldView;
+ private VideoViewInterface mOldView;
+
+
+ public VideoSurfaceView(Context context) {
+ this(context, null);
+ }
+
+ public VideoSurfaceView(Context context, AttributeSet attrs) {
+ this(context, attrs, 0);
+ }
+
+ public VideoSurfaceView(Context context, AttributeSet attrs, int defStyleAttr) {
+ this(context, attrs, defStyleAttr, 0);
+ }
+
+ public VideoSurfaceView(Context context, AttributeSet attrs, int defStyleAttr,
+ int defStyleRes) {
+ super(context, attrs, defStyleAttr, defStyleRes);
+ getHolder().addCallback(this);
+ }
+
+ ////////////////////////////////////////////////////
+ // implements VideoViewInterface
+ ////////////////////////////////////////////////////
+
+ @Override
+ public boolean assignSurfaceToMediaPlayer(MediaPlayer mp) {
+ Log.d(TAG, "assignSurfaceToMediaPlayer(): mSurfaceHolder: " + mSurfaceHolder);
+ if (mp == null || !hasAvailableSurface()) {
+ return false;
+ }
+ mp.setDisplay(mSurfaceHolder);
+ return true;
+ }
+
+ @Override
+ public void setSurfaceListener(SurfaceListener l) {
+ mSurfaceListener = l;
+ }
+
+ @Override
+ public int getViewType() {
+ return VIEW_TYPE_SURFACEVIEW;
+ }
+
+ @Override
+ public void setMediaPlayer(MediaPlayer mp) {
+ mMediaPlayer = mp;
+ if (mIsTakingOverOldView) {
+ takeOver(mOldView);
+ }
+ }
+
+ @Override
+ public void takeOver(@NonNull VideoViewInterface oldView) {
+ if (assignSurfaceToMediaPlayer(mMediaPlayer)) {
+ ((View) oldView).setVisibility(GONE);
+ mIsTakingOverOldView = false;
+ mOldView = null;
+ if (mSurfaceListener != null) {
+ mSurfaceListener.onSurfaceTakeOverDone(this);
+ }
+ } else {
+ mIsTakingOverOldView = true;
+ mOldView = oldView;
+ }
+ }
+
+ @Override
+ public boolean hasAvailableSurface() {
+ return (mSurfaceHolder != null && mSurfaceHolder.getSurface() != null);
+ }
+
+ ////////////////////////////////////////////////////
+ // implements SurfaceHolder.Callback
+ ////////////////////////////////////////////////////
+
+ @Override
+ public void surfaceCreated(SurfaceHolder holder) {
+ Log.d(TAG, "surfaceCreated: mSurfaceHolder: " + mSurfaceHolder + ", new holder: " + holder);
+ mSurfaceHolder = holder;
+ if (mIsTakingOverOldView) {
+ takeOver(mOldView);
+ } else {
+ assignSurfaceToMediaPlayer(mMediaPlayer);
+ }
+
+ if (mSurfaceListener != null) {
+ Rect rect = mSurfaceHolder.getSurfaceFrame();
+ mSurfaceListener.onSurfaceCreated(this, rect.width(), rect.height());
+ }
+ }
+
+ @Override
+ public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {
+ if (mSurfaceListener != null) {
+ mSurfaceListener.onSurfaceChanged(this, width, height);
+ }
+ }
+
+ @Override
+ public void surfaceDestroyed(SurfaceHolder holder) {
+ // After we return from this we can't use the surface any more
+ mSurfaceHolder = null;
+ if (mSurfaceListener != null) {
+ mSurfaceListener.onSurfaceDestroyed(this);
+ }
+ }
+
+ // TODO: Investigate the way to move onMeasure() code into FrameLayout.
+ @Override
+ protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
+ int videoWidth = (mMediaPlayer == null) ? 0 : mMediaPlayer.getVideoWidth();
+ int videoHeight = (mMediaPlayer == null) ? 0 : mMediaPlayer.getVideoHeight();
+ if (DEBUG) {
+ Log.d(TAG, "onMeasure(" + MeasureSpec.toString(widthMeasureSpec) + ", "
+ + MeasureSpec.toString(heightMeasureSpec) + ")");
+ Log.i(TAG, " measuredSize: " + getMeasuredWidth() + "/" + getMeasuredHeight());
+ Log.i(TAG, " viewSize: " + getWidth() + "/" + getHeight());
+ Log.i(TAG, " mVideoWidth/height: " + videoWidth + ", " + videoHeight);
+ }
+
+ int width = getDefaultSize(videoWidth, widthMeasureSpec);
+ int height = getDefaultSize(videoHeight, heightMeasureSpec);
+
+ if (videoWidth > 0 && videoHeight > 0) {
+ int widthSpecMode = MeasureSpec.getMode(widthMeasureSpec);
+ int widthSpecSize = MeasureSpec.getSize(widthMeasureSpec);
+ int heightSpecMode = MeasureSpec.getMode(heightMeasureSpec);
+ int heightSpecSize = MeasureSpec.getSize(heightMeasureSpec);
+
+ if (widthSpecMode == MeasureSpec.EXACTLY && heightSpecMode == MeasureSpec.EXACTLY) {
+ // the size is fixed
+ width = widthSpecSize;
+ height = heightSpecSize;
+
+ // for compatibility, we adjust size based on aspect ratio
+ if (videoWidth * height < width * videoHeight) {
+ if (DEBUG) {
+ Log.d(TAG, "image too wide, correcting");
+ }
+ width = height * videoWidth / videoHeight;
+ } else if (videoWidth * height > width * videoHeight) {
+ if (DEBUG) {
+ Log.d(TAG, "image too tall, correcting");
+ }
+ height = width * videoHeight / videoWidth;
+ }
+ } else if (widthSpecMode == MeasureSpec.EXACTLY) {
+ // only the width is fixed, adjust the height to match aspect ratio if possible
+ width = widthSpecSize;
+ height = width * videoHeight / videoWidth;
+ if (heightSpecMode == MeasureSpec.AT_MOST && height > heightSpecSize) {
+ // couldn't match aspect ratio within the constraints
+ height = heightSpecSize;
+ }
+ } else if (heightSpecMode == MeasureSpec.EXACTLY) {
+ // only the height is fixed, adjust the width to match aspect ratio if possible
+ height = heightSpecSize;
+ width = height * videoWidth / videoHeight;
+ if (widthSpecMode == MeasureSpec.AT_MOST && width > widthSpecSize) {
+ // couldn't match aspect ratio within the constraints
+ width = widthSpecSize;
+ }
+ } else {
+ // neither the width nor the height are fixed, try to use actual video size
+ width = videoWidth;
+ height = videoHeight;
+ if (heightSpecMode == MeasureSpec.AT_MOST && height > heightSpecSize) {
+ // too tall, decrease both width and height
+ height = heightSpecSize;
+ width = height * videoWidth / videoHeight;
+ }
+ if (widthSpecMode == MeasureSpec.AT_MOST && width > widthSpecSize) {
+ // too wide, decrease both width and height
+ width = widthSpecSize;
+ height = width * videoHeight / videoWidth;
+ }
+ }
+ } else {
+ // no size yet, just adopt the given spec sizes
+ }
+ setMeasuredDimension(width, height);
+ if (DEBUG) {
+ Log.i(TAG, "end of onMeasure()");
+ Log.i(TAG, " measuredSize: " + getMeasuredWidth() + "/" + getMeasuredHeight());
+ }
+ }
+
+ @Override
+ public String toString() {
+ return "ViewType: SurfaceView / Visibility: " + getVisibility()
+ + " / surfaceHolder: " + mSurfaceHolder;
+ }
+}
diff --git a/packages/MediaComponents/src/com/android/widget/VideoTextureView.java b/packages/MediaComponents/src/com/android/widget/VideoTextureView.java
new file mode 100644
index 0000000..f240301
--- /dev/null
+++ b/packages/MediaComponents/src/com/android/widget/VideoTextureView.java
@@ -0,0 +1,245 @@
+/*
+ * Copyright 2018 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.android.widget;
+
+import android.content.Context;
+import android.graphics.SurfaceTexture;
+import android.media.MediaPlayer;
+import android.support.annotation.NonNull;
+import android.support.annotation.RequiresApi;
+import android.util.AttributeSet;
+import android.util.Log;
+import android.view.Surface;
+import android.view.TextureView;
+import android.view.View;
+
+import static android.widget.VideoView2.VIEW_TYPE_TEXTUREVIEW;
+
+@RequiresApi(26)
+class VideoTextureView extends TextureView
+ implements VideoViewInterface, TextureView.SurfaceTextureListener {
+ private static final String TAG = "VideoTextureView";
+ private static final boolean DEBUG = true; // STOPSHIP: Log.isLoggable(TAG, Log.DEBUG);
+
+ private SurfaceTexture mSurfaceTexture;
+ private Surface mSurface;
+ private SurfaceListener mSurfaceListener;
+ private MediaPlayer mMediaPlayer;
+ // A flag to indicate taking over other view should be proceed.
+ private boolean mIsTakingOverOldView;
+ private VideoViewInterface mOldView;
+
+ public VideoTextureView(Context context) {
+ this(context, null);
+ }
+
+ public VideoTextureView(Context context, AttributeSet attrs) {
+ this(context, attrs, 0);
+ }
+
+ public VideoTextureView(Context context, AttributeSet attrs, int defStyleAttr) {
+ this(context, attrs, defStyleAttr, 0);
+ }
+
+ public VideoTextureView(
+ Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
+ super(context, attrs, defStyleAttr, defStyleRes);
+ setSurfaceTextureListener(this);
+ }
+
+ ////////////////////////////////////////////////////
+ // implements VideoViewInterface
+ ////////////////////////////////////////////////////
+
+ @Override
+ public boolean assignSurfaceToMediaPlayer(MediaPlayer mp) {
+ Log.d(TAG, "assignSurfaceToMediaPlayer(): mSurfaceTexture: " + mSurfaceTexture);
+ if (mp == null || !hasAvailableSurface()) {
+ // Surface is not ready.
+ return false;
+ }
+ mp.setSurface(mSurface);
+ return true;
+ }
+
+ @Override
+ public void setSurfaceListener(SurfaceListener l) {
+ mSurfaceListener = l;
+ }
+
+ @Override
+ public int getViewType() {
+ return VIEW_TYPE_TEXTUREVIEW;
+ }
+
+ @Override
+ public void setMediaPlayer(MediaPlayer mp) {
+ mMediaPlayer = mp;
+ if (mIsTakingOverOldView) {
+ takeOver(mOldView);
+ }
+ }
+
+ @Override
+ public void takeOver(@NonNull VideoViewInterface oldView) {
+ if (assignSurfaceToMediaPlayer(mMediaPlayer)) {
+ ((View) oldView).setVisibility(GONE);
+ mIsTakingOverOldView = false;
+ mOldView = null;
+ if (mSurfaceListener != null) {
+ mSurfaceListener.onSurfaceTakeOverDone(this);
+ }
+ } else {
+ mIsTakingOverOldView = true;
+ mOldView = oldView;
+ }
+ }
+
+ @Override
+ public boolean hasAvailableSurface() {
+ return (mSurfaceTexture != null && !mSurfaceTexture.isReleased() && mSurface != null);
+ }
+
+ ////////////////////////////////////////////////////
+ // implements TextureView.SurfaceTextureListener
+ ////////////////////////////////////////////////////
+
+ @Override
+ public void onSurfaceTextureAvailable(SurfaceTexture surfaceTexture, int width, int height) {
+ Log.d(TAG, "onSurfaceTextureAvailable: mSurfaceTexture: " + mSurfaceTexture
+ + ", new surface: " + surfaceTexture);
+ mSurfaceTexture = surfaceTexture;
+ mSurface = new Surface(mSurfaceTexture);
+ if (mIsTakingOverOldView) {
+ takeOver(mOldView);
+ } else {
+ assignSurfaceToMediaPlayer(mMediaPlayer);
+ }
+ if (mSurfaceListener != null) {
+ mSurfaceListener.onSurfaceCreated(this, width, height);
+ }
+ }
+
+ @Override
+ public void onSurfaceTextureSizeChanged(SurfaceTexture surfaceTexture, int width, int height) {
+ if (mSurfaceListener != null) {
+ mSurfaceListener.onSurfaceChanged(this, width, height);
+ }
+ // requestLayout(); // TODO: figure out if it should be called here?
+ }
+
+ @Override
+ public void onSurfaceTextureUpdated(SurfaceTexture surface) {
+ // no-op
+ }
+
+ @Override
+ public boolean onSurfaceTextureDestroyed(SurfaceTexture surfaceTexture) {
+ if (mSurfaceListener != null) {
+ mSurfaceListener.onSurfaceDestroyed(this);
+ }
+ mSurfaceTexture = null;
+ mSurface = null;
+ return true;
+ }
+
+ @Override
+ protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
+ int videoWidth = (mMediaPlayer == null) ? 0 : mMediaPlayer.getVideoWidth();
+ int videoHeight = (mMediaPlayer == null) ? 0 : mMediaPlayer.getVideoHeight();
+ if (DEBUG) {
+ Log.d(TAG, "onMeasure(" + MeasureSpec.toString(widthMeasureSpec) + ", "
+ + MeasureSpec.toString(heightMeasureSpec) + ")");
+ Log.i(TAG, " measuredSize: " + getMeasuredWidth() + "/" + getMeasuredHeight());
+ Log.i(TAG, " viewSize: " + getWidth() + "/" + getHeight());
+ Log.i(TAG, " mVideoWidth/height: " + videoWidth + ", " + videoHeight);
+ }
+
+ int width = getDefaultSize(videoWidth, widthMeasureSpec);
+ int height = getDefaultSize(videoHeight, heightMeasureSpec);
+
+ if (videoWidth > 0 && videoHeight > 0) {
+ int widthSpecMode = MeasureSpec.getMode(widthMeasureSpec);
+ int widthSpecSize = MeasureSpec.getSize(widthMeasureSpec);
+ int heightSpecMode = MeasureSpec.getMode(heightMeasureSpec);
+ int heightSpecSize = MeasureSpec.getSize(heightMeasureSpec);
+
+ if (widthSpecMode == MeasureSpec.EXACTLY && heightSpecMode == MeasureSpec.EXACTLY) {
+ // the size is fixed
+ width = widthSpecSize;
+ height = heightSpecSize;
+
+ // for compatibility, we adjust size based on aspect ratio
+ if (videoWidth * height < width * videoHeight) {
+ if (DEBUG) {
+ Log.d(TAG, "image too wide, correcting");
+ }
+ width = height * videoWidth / videoHeight;
+ } else if (videoWidth * height > width * videoHeight) {
+ if (DEBUG) {
+ Log.d(TAG, "image too tall, correcting");
+ }
+ height = width * videoHeight / videoWidth;
+ }
+ } else if (widthSpecMode == MeasureSpec.EXACTLY) {
+ // only the width is fixed, adjust the height to match aspect ratio if possible
+ width = widthSpecSize;
+ height = width * videoHeight / videoWidth;
+ if (heightSpecMode == MeasureSpec.AT_MOST && height > heightSpecSize) {
+ // couldn't match aspect ratio within the constraints
+ height = heightSpecSize;
+ }
+ } else if (heightSpecMode == MeasureSpec.EXACTLY) {
+ // only the height is fixed, adjust the width to match aspect ratio if possible
+ height = heightSpecSize;
+ width = height * videoWidth / videoHeight;
+ if (widthSpecMode == MeasureSpec.AT_MOST && width > widthSpecSize) {
+ // couldn't match aspect ratio within the constraints
+ width = widthSpecSize;
+ }
+ } else {
+ // neither the width nor the height are fixed, try to use actual video size
+ width = videoWidth;
+ height = videoHeight;
+ if (heightSpecMode == MeasureSpec.AT_MOST && height > heightSpecSize) {
+ // too tall, decrease both width and height
+ height = heightSpecSize;
+ width = height * videoWidth / videoHeight;
+ }
+ if (widthSpecMode == MeasureSpec.AT_MOST && width > widthSpecSize) {
+ // too wide, decrease both width and height
+ width = widthSpecSize;
+ height = width * videoHeight / videoWidth;
+ }
+ }
+ } else {
+ // no size yet, just adopt the given spec sizes
+ }
+ setMeasuredDimension(width, height);
+ if (DEBUG) {
+ Log.i(TAG, "end of onMeasure()");
+ Log.i(TAG, " measuredSize: " + getMeasuredWidth() + "/" + getMeasuredHeight());
+ }
+ }
+
+ @Override
+ public String toString() {
+ return "ViewType: TextureView / Visibility: " + getVisibility()
+ + " / surfaceTexture: " + mSurfaceTexture;
+
+ }
+}
diff --git a/packages/MediaComponents/src/com/android/widget/VideoView2Impl.java b/packages/MediaComponents/src/com/android/widget/VideoView2Impl.java
index 66b5ed5..19a41de 100644
--- a/packages/MediaComponents/src/com/android/widget/VideoView2Impl.java
+++ b/packages/MediaComponents/src/com/android/widget/VideoView2Impl.java
@@ -16,220 +16,486 @@
package com.android.widget;
-import android.graphics.Canvas;
+import android.app.AlertDialog;
+import android.content.Context;
+import android.content.DialogInterface;
+import android.content.res.Resources;
import android.media.AudioAttributes;
import android.media.AudioFocusRequest;
+import android.media.AudioManager;
+import android.media.MediaMetadata;
import android.media.MediaPlayer;
+import android.media.Cea708CaptionRenderer;
+import android.media.ClosedCaptionRenderer;
+import android.media.Metadata;
+import android.media.PlaybackParams;
+import android.media.SubtitleController;
+import android.media.session.MediaSession;
+import android.media.session.PlaybackState;
+import android.media.TtmlRenderer;
+import android.media.WebVttRenderer;
import android.media.update.VideoView2Provider;
import android.media.update.ViewProvider;
import android.net.Uri;
+import android.os.Bundle;
+import android.os.ResultReceiver;
+import android.support.annotation.Nullable;
+import android.util.AttributeSet;
+import android.util.Log;
+import android.view.Gravity;
import android.view.KeyEvent;
import android.view.MotionEvent;
import android.view.View;
-import android.widget.MediaController2;
+import android.widget.FrameLayout.LayoutParams;
+import android.widget.MediaControlView2;
import android.widget.VideoView2;
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.List;
import java.util.Map;
+import com.android.media.update.ApiHelper;
+import com.android.media.update.R;
+
public class VideoView2Impl implements VideoView2Provider, VideoViewInterface.SurfaceListener {
+ private static final String TAG = "VideoView2";
+ private static final boolean DEBUG = true; // STOPSHIP: Log.isLoggable(TAG, Log.DEBUG);
private final VideoView2 mInstance;
private final ViewProvider mSuperProvider;
- public VideoView2Impl(VideoView2 instance, ViewProvider superProvider) {
+ private static final int STATE_ERROR = -1;
+ private static final int STATE_IDLE = 0;
+ private static final int STATE_PREPARING = 1;
+ private static final int STATE_PREPARED = 2;
+ private static final int STATE_PLAYING = 3;
+ private static final int STATE_PAUSED = 4;
+ private static final int STATE_PLAYBACK_COMPLETED = 5;
+
+ private final AudioManager mAudioManager;
+ private AudioAttributes mAudioAttributes;
+ private int mAudioFocusType = AudioManager.AUDIOFOCUS_GAIN; // legacy focus gain
+ private int mAudioSession;
+
+ private VideoView2.OnPreparedListener mOnPreparedListener;
+ private VideoView2.OnCompletionListener mOnCompletionListener;
+ private VideoView2.OnErrorListener mOnErrorListener;
+ private VideoView2.OnInfoListener mOnInfoListener;
+ private VideoView2.OnViewTypeChangedListener mOnViewTypeChangedListener;
+
+ private VideoViewInterface mCurrentView;
+ private VideoTextureView mTextureView;
+ private VideoSurfaceView mSurfaceView;
+
+ private MediaPlayer mMediaPlayer;
+ private MediaControlView2 mMediaControlView;
+ private MediaSession mMediaSession;
+
+ private PlaybackState.Builder mStateBuilder;
+ private int mTargetState = STATE_IDLE;
+ private int mCurrentState = STATE_IDLE;
+ private int mCurrentBufferPercentage;
+ private int mSeekWhenPrepared; // recording the seek position while preparing
+
+ private int mSurfaceWidth;
+ private int mSurfaceHeight;
+ private int mVideoWidth;
+ private int mVideoHeight;
+
+ private boolean mCCEnabled;
+ private int mSelectedTrackIndex;
+
+ private SubtitleView mSubtitleView;
+ private float mSpeed;
+ // TODO: Remove mFallbackSpeed when integration with MediaPlayer2's new setPlaybackParams().
+ // Refer: https://docs.google.com/document/d/1nzAfns6i2hJ3RkaUre3QMT6wsDedJ5ONLiA_OOBFFX8/edit
+ private float mFallbackSpeed; // keep the original speed before 'pause' is called.
+
+ public VideoView2Impl(
+ VideoView2 instance, ViewProvider superProvider,
+ @Nullable AttributeSet attrs, int defStyleAttr, int defStyleRes) {
mInstance = instance;
mSuperProvider = superProvider;
- // TODO: Implement
+ mVideoWidth = 0;
+ mVideoHeight = 0;
+ mSurfaceWidth = 0;
+ mSurfaceHeight = 0;
+ mSpeed = 1.0f;
+ mFallbackSpeed = mSpeed;
+
+ mAudioManager = (AudioManager) mInstance.getContext()
+ .getSystemService(Context.AUDIO_SERVICE);
+ mAudioAttributes = new AudioAttributes.Builder().setUsage(AudioAttributes.USAGE_MEDIA)
+ .setContentType(AudioAttributes.CONTENT_TYPE_MOVIE).build();
+ mInstance.setFocusable(true);
+ mInstance.setFocusableInTouchMode(true);
+ mInstance.requestFocus();
+
+ // TODO: try to keep a single child at a time rather than always having both.
+ mTextureView = new VideoTextureView(mInstance.getContext());
+ mSurfaceView = new VideoSurfaceView(mInstance.getContext());
+ LayoutParams params = new LayoutParams(LayoutParams.MATCH_PARENT,
+ LayoutParams.WRAP_CONTENT);
+ mTextureView.setLayoutParams(params);
+ mSurfaceView.setLayoutParams(params);
+ mTextureView.setSurfaceListener(this);
+ mSurfaceView.setSurfaceListener(this);
+
+ // TODO: Choose TextureView when SurfaceView cannot be created.
+ // Choose surface view by default
+ mTextureView.setVisibility(View.GONE);
+ mSurfaceView.setVisibility(View.VISIBLE);
+ mInstance.addView(mTextureView);
+ mInstance.addView(mSurfaceView);
+ mCurrentView = mSurfaceView;
+
+ LayoutParams subtitleParams = new LayoutParams(LayoutParams.MATCH_PARENT,
+ LayoutParams.MATCH_PARENT);
+ mSubtitleView = new SubtitleView(mInstance.getContext());
+ mSubtitleView.setLayoutParams(subtitleParams);
+ mSubtitleView.setBackgroundColor(0);
+ mInstance.addView(mSubtitleView);
+
+ // Create MediaSession
+ mMediaSession = new MediaSession(mInstance.getContext(), "VideoView2MediaSession");
+ mMediaSession.setCallback(new MediaSessionCallback());
+
+ // TODO: Need a common namespace for attributes those are defined in updatable library.
+ boolean enableControlView = (attrs == null) || attrs.getAttributeBooleanValue(
+ "http://schemas.android.com/apk/com.android.media.api_provider",
+ "enableControlView", true);
+ if (enableControlView) {
+ setMediaControlView2_impl(new MediaControlView2(mInstance.getContext()));
+ }
+ }
+
+ @Override
+ public void setMediaControlView2_impl(MediaControlView2 mediaControlView) {
+ mMediaControlView = mediaControlView;
+
+ // TODO: change this so that the CC button appears only where there is a subtitle track.
+ mMediaControlView.showCCButton();
+
+ // Get MediaController from MediaSession and set it inside MediaControlView2
+ mMediaControlView.setController(mMediaSession.getController());
+
+ LayoutParams params =
+ new LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT);
+ mInstance.addView(mMediaControlView, params);
+ }
+
+ @Override
+ public MediaControlView2 getMediaControlView2_impl() {
+ return mMediaControlView;
}
@Override
public void start_impl() {
- // TODO: Implement
+ if (isInPlaybackState() && mCurrentView.hasAvailableSurface()) {
+ applySpeed();
+ mMediaPlayer.start();
+ mCurrentState = STATE_PLAYING;
+ updatePlaybackState();
+ }
+ mTargetState = STATE_PLAYING;
+ if (DEBUG) {
+ Log.d(TAG, "start(). mCurrentState=" + mCurrentState
+ + ", mTargetState=" + mTargetState);
+ }
}
@Override
public void pause_impl() {
- // TODO: Implement
+ if (isInPlaybackState()) {
+ if (mMediaPlayer.isPlaying()) {
+ mMediaPlayer.pause();
+ mCurrentState = STATE_PAUSED;
+ updatePlaybackState();
+ }
+ }
+ mTargetState = STATE_PAUSED;
+ if (DEBUG) {
+ Log.d(TAG, "pause(). mCurrentState=" + mCurrentState
+ + ", mTargetState=" + mTargetState);
+ }
}
@Override
public int getDuration_impl() {
- // TODO: Implement
+ if (isInPlaybackState()) {
+ return mMediaPlayer.getDuration();
+ }
return -1;
}
@Override
public int getCurrentPosition_impl() {
- // TODO: Implement
+ if (isInPlaybackState()) {
+ return mMediaPlayer.getCurrentPosition();
+ }
return 0;
}
@Override
public void seekTo_impl(int msec) {
- // TODO: Implement
+ if (isInPlaybackState()) {
+ mMediaPlayer.seekTo(msec);
+ mSeekWhenPrepared = 0;
+ updatePlaybackState();
+ } else {
+ mSeekWhenPrepared = msec;
+ }
}
@Override
public boolean isPlaying_impl() {
- // TODO: Implement
- return false;
+ return (isInPlaybackState()) && mMediaPlayer.isPlaying();
}
@Override
public int getBufferPercentage_impl() {
- return -1;
+ return mCurrentBufferPercentage;
}
@Override
public int getAudioSessionId_impl() {
- // TODO: Implement
- return 0;
+ if (mAudioSession == 0) {
+ MediaPlayer foo = new MediaPlayer();
+ mAudioSession = foo.getAudioSessionId();
+ foo.release();
+ }
+ return mAudioSession;
}
@Override
public void showSubtitle_impl() {
- // TODO: Implement
+ // Retrieve all tracks that belong to the current video.
+ MediaPlayer.TrackInfo[] trackInfos = mMediaPlayer.getTrackInfo();
+
+ List<Integer> subtitleTrackIndices = new ArrayList<>();
+ for (int i = 0; i < trackInfos.length; ++i) {
+ int trackType = trackInfos[i].getTrackType();
+ if (trackType == MediaPlayer.TrackInfo.MEDIA_TRACK_TYPE_SUBTITLE) {
+ subtitleTrackIndices.add(i);
+ }
+ }
+ if (subtitleTrackIndices.size() > 0) {
+ // Select first subtitle track
+ mCCEnabled = true;
+ mSelectedTrackIndex = subtitleTrackIndices.get(0);
+ mMediaPlayer.selectTrack(mSelectedTrackIndex);
+ }
}
@Override
public void hideSubtitle_impl() {
- // TODO: Implement
+ if (mCCEnabled) {
+ mMediaPlayer.deselectTrack(mSelectedTrackIndex);
+ mCCEnabled = false;
+ }
+ }
+
+ @Override
+ public void setSpeed_impl(float speed) {
+ if (speed <= 0.0f) {
+ Log.e(TAG, "Unsupported speed (" + speed + ") is ignored.");
+ return;
+ }
+ mSpeed = speed;
+ if (mMediaPlayer != null && mMediaPlayer.isPlaying()) {
+ applySpeed();
+ }
+ }
+
+ @Override
+ public float getSpeed_impl() {
+ if (DEBUG) {
+ if (mMediaPlayer != null) {
+ float speed = mMediaPlayer.getPlaybackParams().getSpeed();
+ if (speed != mSpeed) {
+ Log.w(TAG, "VideoView2's speed : " + mSpeed + " is different from "
+ + "MediaPlayer's speed : " + speed);
+ }
+ }
+ }
+ return mSpeed;
}
@Override
public void setAudioFocusRequest_impl(int focusGain) {
- // TODO: Implement
+ if (focusGain != AudioManager.AUDIOFOCUS_NONE
+ && focusGain != AudioManager.AUDIOFOCUS_GAIN
+ && focusGain != AudioManager.AUDIOFOCUS_GAIN_TRANSIENT
+ && focusGain != AudioManager.AUDIOFOCUS_GAIN_TRANSIENT_MAY_DUCK
+ && focusGain != AudioManager.AUDIOFOCUS_GAIN_TRANSIENT_EXCLUSIVE) {
+ throw new IllegalArgumentException("Illegal audio focus type " + focusGain);
+ }
+ mAudioFocusType = focusGain;
}
@Override
public void setAudioAttributes_impl(AudioAttributes attributes) {
- // TODO: Implement
+ if (attributes == null) {
+ throw new IllegalArgumentException("Illegal null AudioAttributes");
+ }
+ mAudioAttributes = attributes;
}
@Override
public void setVideoPath_impl(String path) {
- // TODO: Implement
+ mInstance.setVideoURI(Uri.parse(path));
}
@Override
public void setVideoURI_impl(Uri uri) {
- // TODO: Implement
+ mInstance.setVideoURI(uri, null);
}
@Override
public void setVideoURI_impl(Uri uri, Map<String, String> headers) {
- // TODO: Implement
- }
-
- @Override
- public void setMediaController2_impl(MediaController2 controllerView) {
- // TODO: Implement
+ mSeekWhenPrepared = 0;
+ openVideo(uri, headers);
}
@Override
public void setViewType_impl(int viewType) {
- // TODO: Implement
+ if (viewType == mCurrentView.getViewType()) {
+ return;
+ }
+ VideoViewInterface targetView;
+ if (viewType == VideoView2.VIEW_TYPE_TEXTUREVIEW) {
+ Log.d(TAG, "switching to TextureView");
+ targetView = mTextureView;
+ } else if (viewType == VideoView2.VIEW_TYPE_SURFACEVIEW) {
+ Log.d(TAG, "switching to SurfaceView");
+ targetView = mSurfaceView;
+ } else {
+ throw new IllegalArgumentException("Unknown view type: " + viewType);
+ }
+ ((View) targetView).setVisibility(View.VISIBLE);
+ targetView.takeOver(mCurrentView);
+ mInstance.requestLayout();
}
@Override
public int getViewType_impl() {
- // TODO: Implement
- return -1;
+ return mCurrentView.getViewType();
}
@Override
public void stopPlayback_impl() {
- // TODO: Implement
+ resetPlayer();
}
@Override
- public void setOnPreparedListener_impl(MediaPlayer.OnPreparedListener l) {
- // TODO: Implement
+ public void setOnPreparedListener_impl(VideoView2.OnPreparedListener l) {
+ mOnPreparedListener = l;
}
@Override
- public void setOnCompletionListener_impl(MediaPlayer.OnCompletionListener l) {
- // TODO: Implement
+ public void setOnCompletionListener_impl(VideoView2.OnCompletionListener l) {
+ mOnCompletionListener = l;
}
@Override
- public void setOnErrorListener_impl(MediaPlayer.OnErrorListener l) {
- // TODO: Implement
+ public void setOnErrorListener_impl(VideoView2.OnErrorListener l) {
+ mOnErrorListener = l;
}
@Override
- public void setOnInfoListener_impl(MediaPlayer.OnInfoListener l) {
- // TODO: Implement
+ public void setOnInfoListener_impl(VideoView2.OnInfoListener l) {
+ mOnInfoListener = l;
}
@Override
public void setOnViewTypeChangedListener_impl(VideoView2.OnViewTypeChangedListener l) {
- // TODO: Implement
- }
-
- @Override
- public void onAttachedToWindow_impl() {
- mSuperProvider.onAttachedToWindow_impl();
- // TODO: Implement
- }
-
- @Override
- public void onDetachedFromWindow_impl() {
- mSuperProvider.onDetachedFromWindow_impl();
- // TODO: Implement
- }
-
- @Override
- public void onLayout_impl(boolean changed, int left, int top, int right, int bottom) {
- mSuperProvider.onLayout_impl(changed, left, top, right, bottom);
- // TODO: Implement
- }
-
- @Override
- public void draw_impl(Canvas canvas) {
- mSuperProvider.draw_impl(canvas);
- // TODO: Implement
+ mOnViewTypeChangedListener = l;
}
@Override
public CharSequence getAccessibilityClassName_impl() {
- // TODO: Implement
- return null;
+ return VideoView2.class.getName();
}
@Override
public boolean onTouchEvent_impl(MotionEvent ev) {
- // TODO: Implement
- return false;
+ if (DEBUG) {
+ Log.d(TAG, "onTouchEvent(). mCurrentState=" + mCurrentState
+ + ", mTargetState=" + mTargetState);
+ }
+ if (ev.getAction() == MotionEvent.ACTION_UP
+ && isInPlaybackState() && mMediaControlView != null) {
+ toggleMediaControlViewVisibility();
+ }
+ return mSuperProvider.onTouchEvent_impl(ev);
}
@Override
public boolean onTrackballEvent_impl(MotionEvent ev) {
- // TODO: Implement
- return false;
+ if (ev.getAction() == MotionEvent.ACTION_UP
+ && isInPlaybackState() && mMediaControlView != null) {
+ toggleMediaControlViewVisibility();
+ }
+ return mSuperProvider.onTrackballEvent_impl(ev);
}
@Override
public boolean onKeyDown_impl(int keyCode, KeyEvent event) {
- // TODO: Implement
- return false;
+ Log.v(TAG, "onKeyDown_impl: " + keyCode);
+ boolean isKeyCodeSupported = keyCode != KeyEvent.KEYCODE_BACK
+ && keyCode != KeyEvent.KEYCODE_VOLUME_UP
+ && keyCode != KeyEvent.KEYCODE_VOLUME_DOWN
+ && keyCode != KeyEvent.KEYCODE_VOLUME_MUTE
+ && keyCode != KeyEvent.KEYCODE_MENU
+ && keyCode != KeyEvent.KEYCODE_CALL
+ && keyCode != KeyEvent.KEYCODE_ENDCALL;
+ if (isInPlaybackState() && isKeyCodeSupported && mMediaControlView != null) {
+ if (keyCode == KeyEvent.KEYCODE_HEADSETHOOK
+ || keyCode == KeyEvent.KEYCODE_MEDIA_PLAY_PAUSE) {
+ if (mMediaPlayer.isPlaying()) {
+ mInstance.pause();
+ mMediaControlView.show();
+ } else {
+ mInstance.start();
+ mMediaControlView.hide();
+ }
+ return true;
+ } else if (keyCode == KeyEvent.KEYCODE_MEDIA_PLAY) {
+ if (!mMediaPlayer.isPlaying()) {
+ mInstance.start();
+ mMediaControlView.hide();
+ }
+ return true;
+ } else if (keyCode == KeyEvent.KEYCODE_MEDIA_STOP
+ || keyCode == KeyEvent.KEYCODE_MEDIA_PAUSE) {
+ if (mMediaPlayer.isPlaying()) {
+ mInstance.pause();
+ mMediaControlView.show();
+ }
+ return true;
+ } else {
+ toggleMediaControlViewVisibility();
+ }
+ }
+
+ return mSuperProvider.onKeyDown_impl(keyCode, event);
}
@Override
public void onFinishInflate_impl() {
- // TODO: Implement
+ mSuperProvider.onFinishInflate_impl();
}
@Override
public boolean dispatchKeyEvent_impl(KeyEvent event) {
- // TODO: Implement
- return false;
+ return mSuperProvider.dispatchKeyEvent_impl(event);
}
@Override
public void setEnabled_impl(boolean enabled) {
- // TODO: Implement
+ mSuperProvider.setEnabled_impl(enabled);
}
///////////////////////////////////////////////////
@@ -238,21 +504,484 @@
@Override
public void onSurfaceCreated(View view, int width, int height) {
- // TODO: Implement
+ if (DEBUG) {
+ Log.d(TAG, "onSurfaceCreated(). mCurrentState=" + mCurrentState
+ + ", mTargetState=" + mTargetState + ", width/height: " + width + "/" + height
+ + ", " + view.toString());
+ }
+ mSurfaceWidth = width;
+ mSurfaceHeight = height;
+
+ if (needToStart()) {
+ mInstance.start();
+ }
}
@Override
public void onSurfaceDestroyed(View view) {
- // TODO: Implement
+ if (DEBUG) {
+ Log.d(TAG, "onSurfaceDestroyed(). mCurrentState=" + mCurrentState
+ + ", mTargetState=" + mTargetState + ", " + view.toString());
+ }
+ if (mMediaControlView != null) {
+ mMediaControlView.hide();
+ }
}
@Override
public void onSurfaceChanged(View view, int width, int height) {
- // TODO: Implement
+ // TODO: Do we need to call requestLayout here?
+ if (DEBUG) {
+ Log.d(TAG, "onSurfaceChanged(). width/height: " + width + "/" + height
+ + ", " + view.toString());
+ }
+ mSurfaceWidth = width;
+ mSurfaceHeight = height;
}
@Override
public void onSurfaceTakeOverDone(VideoViewInterface view) {
- // TODO: Implement
+ if (DEBUG) {
+ Log.d(TAG, "onSurfaceTakeOverDone(). Now current view is: " + view);
+ }
+ mCurrentView = view;
+ if (mOnViewTypeChangedListener != null) {
+ mOnViewTypeChangedListener.onViewTypeChanged(view.getViewType());
+ }
+ if (needToStart()) {
+ mInstance.start();
+ }
+ }
+
+ ///////////////////////////////////////////////////
+ // Protected or private methods
+ ///////////////////////////////////////////////////
+
+ private boolean isInPlaybackState() {
+ return (mMediaPlayer != null
+ && mCurrentState != STATE_ERROR
+ && mCurrentState != STATE_IDLE
+ && mCurrentState != STATE_PREPARING);
+ }
+
+ private boolean needToStart() {
+ return (mMediaPlayer != null
+ && mCurrentState != STATE_PLAYING
+ && mTargetState == STATE_PLAYING);
+ }
+
+ // Creates a MediaPlayer instance and prepare playback.
+ private void openVideo(Uri uri, Map<String, String> headers) {
+ resetPlayer();
+ if (mAudioFocusType != AudioManager.AUDIOFOCUS_NONE) {
+ // TODO this should have a focus listener
+ AudioFocusRequest focusRequest;
+ focusRequest = new AudioFocusRequest.Builder(mAudioFocusType)
+ .setAudioAttributes(mAudioAttributes)
+ .build();
+ mAudioManager.requestAudioFocus(focusRequest);
+ }
+
+ try {
+ Log.d(TAG, "openVideo(): creating new MediaPlayer instance.");
+ mMediaPlayer = new MediaPlayer();
+ mSurfaceView.setMediaPlayer(mMediaPlayer);
+ mTextureView.setMediaPlayer(mMediaPlayer);
+ mCurrentView.assignSurfaceToMediaPlayer(mMediaPlayer);
+
+ // TODO: create SubtitleController in MediaPlayer, but we need
+ // a context for the subtitle renderers
+ final Context context = mInstance.getContext();
+ final SubtitleController controller = new SubtitleController(
+ context, mMediaPlayer.getMediaTimeProvider(), mMediaPlayer);
+ controller.registerRenderer(new WebVttRenderer(context));
+ controller.registerRenderer(new TtmlRenderer(context));
+ controller.registerRenderer(new Cea708CaptionRenderer(context));
+ controller.registerRenderer(new ClosedCaptionRenderer(context));
+ mMediaPlayer.setSubtitleAnchor(controller, (SubtitleController.Anchor) mSubtitleView);
+
+ if (mAudioSession != 0) {
+ mMediaPlayer.setAudioSessionId(mAudioSession);
+ } else {
+ mAudioSession = mMediaPlayer.getAudioSessionId();
+ }
+ mMediaPlayer.setOnPreparedListener(mPreparedListener);
+ mMediaPlayer.setOnVideoSizeChangedListener(mSizeChangedListener);
+ mMediaPlayer.setOnCompletionListener(mCompletionListener);
+ mMediaPlayer.setOnErrorListener(mErrorListener);
+ mMediaPlayer.setOnInfoListener(mInfoListener);
+ mMediaPlayer.setOnBufferingUpdateListener(mBufferingUpdateListener);
+ mCurrentBufferPercentage = 0;
+ mMediaPlayer.setDataSource(mInstance.getContext(), uri, headers);
+ mMediaPlayer.setAudioAttributes(mAudioAttributes);
+ // we don't set the target state here either, but preserve the
+ // target state that was there before.
+ mCurrentState = STATE_PREPARING;
+ mMediaPlayer.prepareAsync();
+
+ if (DEBUG) {
+ Log.d(TAG, "openVideo(). mCurrentState=" + mCurrentState
+ + ", mTargetState=" + mTargetState);
+ }
+ /*
+ for (Pair<InputStream, MediaFormat> pending: mPendingSubtitleTracks) {
+ try {
+ mMediaPlayer.addSubtitleSource(pending.first, pending.second);
+ } catch (IllegalStateException e) {
+ mInfoListener.onInfo(
+ mMediaPlayer, MediaPlayer.MEDIA_INFO_UNSUPPORTED_SUBTITLE, 0);
+ }
+ }
+ */
+ } catch (IOException | IllegalArgumentException ex) {
+ Log.w(TAG, "Unable to open content: " + uri, ex);
+ mCurrentState = STATE_ERROR;
+ mTargetState = STATE_ERROR;
+ mErrorListener.onError(mMediaPlayer,
+ MediaPlayer.MEDIA_ERROR_UNKNOWN, MediaPlayer.MEDIA_ERROR_IO);
+ } finally {
+ //mPendingSubtitleTracks.clear();
+ }
+ }
+
+ /*
+ * Reset the media player in any state
+ */
+ // TODO: Figure out if the legacy code's boolean parameter: cleartargetstate is necessary.
+ private void resetPlayer() {
+ if (mMediaPlayer != null) {
+ mMediaPlayer.reset();
+ mMediaPlayer.release();
+ mMediaPlayer = null;
+ //mPendingSubtitleTracks.clear();
+ mCurrentState = STATE_IDLE;
+ mTargetState = STATE_IDLE;
+ if (mAudioFocusType != AudioManager.AUDIOFOCUS_NONE) {
+ mAudioManager.abandonAudioFocus(null);
+ }
+ }
+ mSurfaceWidth = 0;
+ mSurfaceHeight = 0;
+ mVideoWidth = 0;
+ mVideoHeight = 0;
+ }
+
+ private void updatePlaybackState() {
+ if (mStateBuilder == null) {
+ // Get the capabilities of the player for this stream
+ Metadata data = mMediaPlayer.getMetadata(MediaPlayer.METADATA_ALL,
+ MediaPlayer.BYPASS_METADATA_FILTER);
+
+ // Add Play action as default
+ long playbackActions = PlaybackState.ACTION_PLAY;
+ if (data != null) {
+ if (!data.has(Metadata.PAUSE_AVAILABLE)
+ || data.getBoolean(Metadata.PAUSE_AVAILABLE)) {
+ playbackActions |= PlaybackState.ACTION_PAUSE;
+ }
+ if (!data.has(Metadata.SEEK_BACKWARD_AVAILABLE)
+ || data.getBoolean(Metadata.SEEK_BACKWARD_AVAILABLE)) {
+ playbackActions |= PlaybackState.ACTION_REWIND;
+ }
+ if (!data.has(Metadata.SEEK_FORWARD_AVAILABLE)
+ || data.getBoolean(Metadata.SEEK_FORWARD_AVAILABLE)) {
+ playbackActions |= PlaybackState.ACTION_FAST_FORWARD;
+ }
+ if (!data.has(Metadata.SEEK_AVAILABLE)
+ || data.getBoolean(Metadata.SEEK_AVAILABLE)) {
+ playbackActions |= PlaybackState.ACTION_SEEK_TO;
+ }
+ } else {
+ playbackActions |= (PlaybackState.ACTION_PAUSE |
+ PlaybackState.ACTION_REWIND | PlaybackState.ACTION_FAST_FORWARD |
+ PlaybackState.ACTION_SEEK_TO);
+ }
+ mStateBuilder = new PlaybackState.Builder();
+ mStateBuilder.setActions(playbackActions);
+ mStateBuilder.addCustomAction(MediaControlView2Impl.ACTION_SHOW_SUBTITLE, null, -1);
+ mStateBuilder.addCustomAction(MediaControlView2Impl.ACTION_HIDE_SUBTITLE, null, -1);
+ }
+ mStateBuilder.setState(getCorrespondingPlaybackState(),
+ mInstance.getCurrentPosition(), 1.0f);
+ mStateBuilder.setBufferedPosition(
+ (long) (mCurrentBufferPercentage / 100.0) * mInstance.getDuration());
+
+ // Set PlaybackState for MediaSession
+ if (mMediaSession != null) {
+ PlaybackState state = mStateBuilder.build();
+ mMediaSession.setPlaybackState(state);
+ }
+ }
+
+ private int getCorrespondingPlaybackState() {
+ switch (mCurrentState) {
+ case STATE_ERROR:
+ return PlaybackState.STATE_ERROR;
+ case STATE_IDLE:
+ return PlaybackState.STATE_NONE;
+ case STATE_PREPARING:
+ return PlaybackState.STATE_CONNECTING;
+ case STATE_PREPARED:
+ return PlaybackState.STATE_STOPPED;
+ case STATE_PLAYING:
+ return PlaybackState.STATE_PLAYING;
+ case STATE_PAUSED:
+ return PlaybackState.STATE_PAUSED;
+ case STATE_PLAYBACK_COMPLETED:
+ return PlaybackState.STATE_STOPPED;
+ default:
+ return -1;
+ }
+ }
+
+ private void toggleMediaControlViewVisibility() {
+ if (mMediaControlView.isShowing()) {
+ mMediaControlView.hide();
+ } else {
+ mMediaControlView.show();
+ }
+ }
+
+ private void applySpeed() {
+ PlaybackParams params = mMediaPlayer.getPlaybackParams().allowDefaults();
+ if (mSpeed != params.getSpeed()) {
+ try {
+ params.setSpeed(mSpeed);
+ mMediaPlayer.setPlaybackParams(params);
+ mFallbackSpeed = mSpeed;
+ } catch (IllegalArgumentException e) {
+ Log.e(TAG, "PlaybackParams has unsupported value: " + e);
+ // TODO: should revise this part after integrating with MP2.
+ // If mSpeed had an illegal value for speed rate, system will determine best
+ // handling (see PlaybackParams.AUDIO_FALLBACK_MODE_DEFAULT).
+ // Note: The pre-MP2 returns 0.0f when it is paused. In this case, VideoView2 will
+ // use mFallbackSpeed instead.
+ float fallbackSpeed = mMediaPlayer.getPlaybackParams().allowDefaults().getSpeed();
+ if (fallbackSpeed > 0.0f) {
+ mFallbackSpeed = fallbackSpeed;
+ }
+ mSpeed = mFallbackSpeed;
+ }
+ }
+ }
+
+ MediaPlayer.OnVideoSizeChangedListener mSizeChangedListener =
+ new MediaPlayer.OnVideoSizeChangedListener() {
+ public void onVideoSizeChanged(MediaPlayer mp, int width, int height) {
+ if (DEBUG) {
+ Log.d(TAG, "OnVideoSizeChanged(): size: " + width + "/" + height);
+ }
+ mVideoWidth = mp.getVideoWidth();
+ mVideoHeight = mp.getVideoHeight();
+ if (DEBUG) {
+ Log.d(TAG, "OnVideoSizeChanged(): mVideoSize:" + mVideoWidth + "/"
+ + mVideoHeight);
+ }
+
+ if (mVideoWidth != 0 && mVideoHeight != 0) {
+ mInstance.requestLayout();
+ }
+ }
+ };
+
+ MediaPlayer.OnPreparedListener mPreparedListener = new MediaPlayer.OnPreparedListener() {
+ public void onPrepared(MediaPlayer mp) {
+ if (DEBUG) {
+ Log.d(TAG, "OnPreparedListener(). mCurrentState=" + mCurrentState
+ + ", mTargetState=" + mTargetState);
+ }
+ mCurrentState = STATE_PREPARED;
+ if (mOnPreparedListener != null) {
+ mOnPreparedListener.onPrepared();
+ }
+ if (mMediaControlView != null) {
+ mMediaControlView.setEnabled(true);
+ }
+ mVideoWidth = mp.getVideoWidth();
+ mVideoHeight = mp.getVideoHeight();
+
+ // mSeekWhenPrepared may be changed after seekTo() call
+ int seekToPosition = mSeekWhenPrepared;
+ if (seekToPosition != 0) {
+ mInstance.seekTo(seekToPosition);
+ }
+
+ // Create and set playback state for MediaControlView2
+ updatePlaybackState();
+
+ // Get and set duration value as MediaMetadata for MediaControlView2
+ MediaMetadata.Builder builder = new MediaMetadata.Builder();
+ builder.putLong(MediaMetadata.METADATA_KEY_DURATION, mInstance.getDuration());
+ if (mMediaSession != null) {
+ mMediaSession.setMetadata(builder.build());
+ }
+
+ if (mVideoWidth != 0 && mVideoHeight != 0) {
+ if (mVideoWidth != mSurfaceWidth || mVideoHeight != mSurfaceHeight) {
+ if (DEBUG) {
+ Log.i(TAG, "OnPreparedListener() : ");
+ Log.i(TAG, " video size: " + mVideoWidth + "/" + mVideoHeight);
+ Log.i(TAG, " surface size: " + mSurfaceWidth + "/" + mSurfaceHeight);
+ Log.i(TAG, " measuredSize: " + mInstance.getMeasuredWidth() + "/"
+ + mInstance.getMeasuredHeight());
+ Log.i(TAG, " viewSize: " + mInstance.getWidth() + "/"
+ + mInstance.getHeight());
+ }
+
+ // TODO: It seems like that overriding onMeasure() is needed like legacy code.
+ mSurfaceWidth = mVideoWidth;
+ mSurfaceHeight = mVideoHeight;
+ mInstance.requestLayout();
+ }
+ if (mSurfaceWidth == mVideoWidth && mSurfaceHeight == mVideoHeight) {
+ // We didn't actually change the size (it was already at the size
+ // we need), so we won't get a "surface changed" callback, so
+ // start the video here instead of in the callback.
+ if (needToStart()) {
+ mInstance.start();
+ if (mMediaControlView != null) {
+ mMediaControlView.show();
+ }
+ } else if (!mInstance.isPlaying() && (seekToPosition != 0
+ || mInstance.getCurrentPosition() > 0)) {
+ if (mMediaControlView != null) {
+ // Show the media controls when we're paused into a video and
+ // make them stick.
+ mMediaControlView.show(0);
+ }
+ }
+ }
+ } else {
+ // We don't know the video size yet, but should start anyway.
+ // The video size might be reported to us later.
+ if (needToStart()) {
+ mInstance.start();
+ }
+ }
+ }
+ };
+
+ private MediaPlayer.OnCompletionListener mCompletionListener =
+ new MediaPlayer.OnCompletionListener() {
+ public void onCompletion(MediaPlayer mp) {
+ mCurrentState = STATE_PLAYBACK_COMPLETED;
+ mTargetState = STATE_PLAYBACK_COMPLETED;
+ updatePlaybackState();
+
+ if (mMediaControlView != null) {
+ mMediaControlView.hide();
+ }
+ if (mOnCompletionListener != null) {
+ mOnCompletionListener.onCompletion();
+ }
+ if (mAudioFocusType != AudioManager.AUDIOFOCUS_NONE) {
+ mAudioManager.abandonAudioFocus(null);
+ }
+ }
+ };
+
+ private MediaPlayer.OnInfoListener mInfoListener =
+ new MediaPlayer.OnInfoListener() {
+ public boolean onInfo(MediaPlayer mp, int what, int extra) {
+ if (mOnInfoListener != null) {
+ mOnInfoListener.onInfo(what, extra);
+ }
+ return true;
+ }
+ };
+
+ private MediaPlayer.OnErrorListener mErrorListener =
+ new MediaPlayer.OnErrorListener() {
+ public boolean onError(MediaPlayer mp, int frameworkErr, int implErr) {
+ if (DEBUG) {
+ Log.d(TAG, "Error: " + frameworkErr + "," + implErr);
+ }
+ mCurrentState = STATE_ERROR;
+ mTargetState = STATE_ERROR;
+ updatePlaybackState();
+
+ if (mMediaControlView != null) {
+ mMediaControlView.hide();
+ }
+
+ /* If an error handler has been supplied, use it and finish. */
+ if (mOnErrorListener != null) {
+ if (mOnErrorListener.onError(frameworkErr, implErr)) {
+ return true;
+ }
+ }
+
+ /* Otherwise, pop up an error dialog so the user knows that
+ * something bad has happened. Only try and pop up the dialog
+ * if we're attached to a window. When we're going away and no
+ * longer have a window, don't bother showing the user an error.
+ */
+ if (mInstance.getWindowToken() != null) {
+ int messageId;
+
+ if (frameworkErr
+ == MediaPlayer.MEDIA_ERROR_NOT_VALID_FOR_PROGRESSIVE_PLAYBACK) {
+ messageId = R.string.VideoView2_error_text_invalid_progressive_playback;
+ } else {
+ messageId = R.string.VideoView2_error_text_unknown;
+ }
+
+ Resources res = ApiHelper.getLibResources();
+ new AlertDialog.Builder(mInstance.getContext())
+ .setMessage(res.getString(messageId))
+ .setPositiveButton(res.getString(R.string.VideoView2_error_button),
+ new DialogInterface.OnClickListener() {
+ public void onClick(DialogInterface dialog,
+ int whichButton) {
+ /* If we get here, there is no onError listener, so
+ * at least inform them that the video is over.
+ */
+ if (mOnCompletionListener != null) {
+ mOnCompletionListener.onCompletion();
+ }
+ }
+ })
+ .setCancelable(false)
+ .show();
+ }
+ return true;
+ }
+ };
+
+ private MediaPlayer.OnBufferingUpdateListener mBufferingUpdateListener =
+ new MediaPlayer.OnBufferingUpdateListener() {
+ public void onBufferingUpdate(MediaPlayer mp, int percent) {
+ mCurrentBufferPercentage = percent;
+ updatePlaybackState();
+ }
+ };
+
+ private class MediaSessionCallback extends MediaSession.Callback {
+ @Override
+ public void onCommand(String command, Bundle args, ResultReceiver receiver) {
+ switch (command) {
+ case MediaControlView2Impl.ACTION_SHOW_SUBTITLE:
+ mInstance.showSubtitle();
+ break;
+ case MediaControlView2Impl.ACTION_HIDE_SUBTITLE:
+ mInstance.hideSubtitle();
+ break;
+ }
+ }
+
+ @Override
+ public void onPlay() {
+ mInstance.start();
+ }
+
+ @Override
+ public void onPause() {
+ mInstance.pause();
+ }
+
+ @Override
+ public void onSeekTo(long pos) {
+ mInstance.seekTo((int) pos);
+ }
}
}