Update ExoPlayer usage (#3760)

* Switch to StyledPlayerView for migration

* Migrate to media3 for ExoPlayer

* Replace deprecated code

* Restore/customize layout to ExoPlayer v2
This commit is contained in:
Joris Pelgröm 2023-08-05 02:07:52 +02:00 committed by GitHub
parent 77141c254b
commit 49f3080b9e
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
13 changed files with 188 additions and 185 deletions

View file

@ -159,11 +159,9 @@ dependencies {
implementation(libs.biometric) implementation(libs.biometric)
implementation(libs.webkit) implementation(libs.webkit)
implementation(libs.exoplayer.core) implementation(libs.bundles.media3)
implementation(libs.exoplayer.hls) "fullImplementation"(libs.media3.datasource.cronet)
implementation(libs.exoplayer.ui) "minimalImplementation"(libs.media3.datasource.cronet) {
"fullImplementation"(libs.extension.cronet)
"minimalImplementation"(libs.extension.cronet) {
exclude(group = "com.google.android.gms", module = "play-services-cronet") exclude(group = "com.google.android.gms", module = "play-services-cronet")
} }
"minimalImplementation"(libs.cronet.embedded) "minimalImplementation"(libs.cronet.embedded)

View file

@ -42,15 +42,15 @@ import android.webkit.WebResourceRequest
import android.webkit.WebResourceResponse import android.webkit.WebResourceResponse
import android.webkit.WebView import android.webkit.WebView
import android.widget.FrameLayout import android.widget.FrameLayout
import android.widget.ImageView import android.widget.ImageButton
import android.widget.Toast import android.widget.Toast
import androidx.activity.OnBackPressedCallback import androidx.activity.OnBackPressedCallback
import androidx.activity.result.IntentSenderRequest import androidx.activity.result.IntentSenderRequest
import androidx.activity.result.contract.ActivityResultContracts import androidx.activity.result.contract.ActivityResultContracts
import androidx.annotation.OptIn
import androidx.annotation.RequiresApi import androidx.annotation.RequiresApi
import androidx.appcompat.app.AlertDialog import androidx.appcompat.app.AlertDialog
import androidx.core.app.ActivityCompat import androidx.core.app.ActivityCompat
import androidx.core.content.ContextCompat
import androidx.core.content.getSystemService import androidx.core.content.getSystemService
import androidx.core.content.res.ResourcesCompat import androidx.core.content.res.ResourcesCompat
import androidx.core.graphics.ColorUtils import androidx.core.graphics.ColorUtils
@ -59,17 +59,17 @@ import androidx.core.view.WindowInsetsControllerCompat
import androidx.lifecycle.Lifecycle import androidx.lifecycle.Lifecycle
import androidx.lifecycle.lifecycleScope import androidx.lifecycle.lifecycleScope
import androidx.lifecycle.repeatOnLifecycle import androidx.lifecycle.repeatOnLifecycle
import androidx.media3.common.MediaItem
import androidx.media3.common.Player
import androidx.media3.common.VideoSize
import androidx.media3.datasource.cronet.CronetDataSource
import androidx.media3.exoplayer.DefaultLoadControl
import androidx.media3.exoplayer.ExoPlayer
import androidx.media3.exoplayer.source.DefaultMediaSourceFactory
import androidx.media3.ui.AspectRatioFrameLayout
import androidx.media3.ui.PlayerView
import androidx.webkit.WebViewCompat import androidx.webkit.WebViewCompat
import androidx.webkit.WebViewFeature import androidx.webkit.WebViewFeature
import com.google.android.exoplayer2.DefaultLoadControl
import com.google.android.exoplayer2.MediaItem
import com.google.android.exoplayer2.Player
import com.google.android.exoplayer2.SimpleExoPlayer
import com.google.android.exoplayer2.ext.cronet.CronetDataSource
import com.google.android.exoplayer2.source.DefaultMediaSourceFactory
import com.google.android.exoplayer2.ui.AspectRatioFrameLayout
import com.google.android.exoplayer2.ui.PlayerView
import com.google.android.exoplayer2.video.VideoSize
import dagger.hilt.android.AndroidEntryPoint import dagger.hilt.android.AndroidEntryPoint
import eightbitlab.com.blurview.RenderScriptBlur import eightbitlab.com.blurview.RenderScriptBlur
import io.homeassistant.companion.android.BaseActivity import io.homeassistant.companion.android.BaseActivity
@ -85,7 +85,6 @@ import io.homeassistant.companion.android.database.authentication.Authentication
import io.homeassistant.companion.android.database.authentication.AuthenticationDao import io.homeassistant.companion.android.database.authentication.AuthenticationDao
import io.homeassistant.companion.android.databinding.ActivityWebviewBinding import io.homeassistant.companion.android.databinding.ActivityWebviewBinding
import io.homeassistant.companion.android.databinding.DialogAuthenticationBinding import io.homeassistant.companion.android.databinding.DialogAuthenticationBinding
import io.homeassistant.companion.android.databinding.ExoPlayerViewBinding
import io.homeassistant.companion.android.launch.LaunchActivity import io.homeassistant.companion.android.launch.LaunchActivity
import io.homeassistant.companion.android.matter.MatterFrontendCommissioningStatus import io.homeassistant.companion.android.matter.MatterFrontendCommissioningStatus
import io.homeassistant.companion.android.nfc.WriteNfcTag import io.homeassistant.companion.android.nfc.WriteNfcTag
@ -118,6 +117,7 @@ import java.util.concurrent.Executors
import javax.inject.Inject import javax.inject.Inject
import io.homeassistant.companion.android.common.R as commonR import io.homeassistant.companion.android.common.R as commonR
@OptIn(androidx.media3.common.util.UnstableApi::class)
@AndroidEntryPoint @AndroidEntryPoint
class WebViewActivity : BaseActivity(), io.homeassistant.companion.android.webview.WebView { class WebViewActivity : BaseActivity(), io.homeassistant.companion.android.webview.WebView {
@ -196,7 +196,6 @@ class WebViewActivity : BaseActivity(), io.homeassistant.companion.android.webvi
private lateinit var myCustomView: View private lateinit var myCustomView: View
private lateinit var authenticator: Authenticator private lateinit var authenticator: Authenticator
private lateinit var exoPlayerView: PlayerView private lateinit var exoPlayerView: PlayerView
private lateinit var playerBinding: ExoPlayerViewBinding
private lateinit var windowInsetsController: WindowInsetsControllerCompat private lateinit var windowInsetsController: WindowInsetsControllerCompat
private var mFilePathCallback: ValueCallback<Array<Uri>>? = null private var mFilePathCallback: ValueCallback<Array<Uri>>? = null
@ -208,13 +207,13 @@ class WebViewActivity : BaseActivity(), io.homeassistant.companion.android.webvi
private var firstAuthTime: Long = 0 private var firstAuthTime: Long = 0
private var resourceURL: String = "" private var resourceURL: String = ""
private var appLocked = true private var appLocked = true
private var exoPlayer: SimpleExoPlayer? = null private var exoPlayer: ExoPlayer? = null
private var isExoFullScreen = false private var isExoFullScreen = false
private var exoTop: Int = 0 // These margins are from the DOM and scaled to screen private var exoTop = 0 // These margins are from the DOM and scaled to screen
private var exoLeft: Int = 0 private var exoLeft = 0
private var exoRight: Int = 0 private var exoRight = 0
private var exoBottom: Int = 0 private var exoBottom = 0
private var exoMute: Boolean = true private var exoMute = true
private var failedConnection = "external" private var failedConnection = "external"
private var clearHistory = false private var clearHistory = false
private var moreInfoEntity = "" private var moreInfoEntity = ""
@ -253,12 +252,8 @@ class WebViewActivity : BaseActivity(), io.homeassistant.companion.android.webvi
exoPlayerView.visibility = View.GONE exoPlayerView.visibility = View.GONE
exoPlayerView.setBackgroundColor(Color.BLACK) exoPlayerView.setBackgroundColor(Color.BLACK)
exoPlayerView.alpha = 1f exoPlayerView.alpha = 1f
exoPlayerView.setShowBuffering(PlayerView.SHOW_BUFFERING_ALWAYS)
exoPlayerView.controllerHideOnTouch = true
exoPlayerView.controllerShowTimeoutMs = 2000 exoPlayerView.controllerShowTimeoutMs = 2000
playerBinding = ExoPlayerViewBinding.bind(exoPlayerView)
appLocked = presenter.isAppLocked() appLocked = presenter.isAppLocked()
binding.blurView.setBlurEnabled(appLocked) binding.blurView.setBlurEnabled(appLocked)
@ -852,7 +847,7 @@ class WebViewActivity : BaseActivity(), io.homeassistant.companion.android.webvi
val uri = Uri.parse(payload.getString("url")) val uri = Uri.parse(payload.getString("url"))
exoMute = payload.optBoolean("muted") exoMute = payload.optBoolean("muted")
runOnUiThread { runOnUiThread {
exoPlayer = SimpleExoPlayer.Builder(applicationContext).setMediaSourceFactory( exoPlayer = ExoPlayer.Builder(applicationContext).setMediaSourceFactory(
DefaultMediaSourceFactory( DefaultMediaSourceFactory(
CronetDataSource.Factory( CronetDataSource.Factory(
CronetEngine.Builder(applicationContext).enableQuic(true).build(), CronetEngine.Builder(applicationContext).enableQuic(true).build(),
@ -872,6 +867,7 @@ class WebViewActivity : BaseActivity(), io.homeassistant.companion.android.webvi
exoPlayer?.addListener(object : Player.Listener { exoPlayer?.addListener(object : Player.Listener {
override fun onVideoSizeChanged(videoSize: VideoSize) { override fun onVideoSizeChanged(videoSize: VideoSize) {
super.onVideoSizeChanged(videoSize) super.onVideoSizeChanged(videoSize)
if (videoSize.height == 0 || videoSize.width == 0) return
exoBottom = exoBottom =
exoTop + ((exoRight - exoLeft) * videoSize.height / videoSize.width) exoTop + ((exoRight - exoLeft) * videoSize.height / videoSize.width)
runOnUiThread { runOnUiThread {
@ -880,16 +876,15 @@ class WebViewActivity : BaseActivity(), io.homeassistant.companion.android.webvi
} }
}) })
exoPlayer?.prepare() exoPlayer?.prepare()
exoMute = !exoMute exoMute = !exoMute // Invert because exoToggleMute() will invert again
exoToggleMute() exoToggleMute()
exoPlayerView.player = exoPlayer exoPlayerView.setFullscreenButtonClickListener { isFullScreen ->
exoPlayerView.visibility = View.VISIBLE isExoFullScreen = isFullScreen
findViewById<ImageView>(R.id.exo_fullscreen_icon).setOnClickListener {
isExoFullScreen = !isExoFullScreen
exoResizeLayout() exoResizeLayout()
} }
findViewById<ImageView>(R.id.exo_mute_icon).setOnClickListener { exoToggleMute() } exoPlayerView.player = exoPlayer
exoPlayerView.visibility = View.VISIBLE
findViewById<ImageButton>(R.id.exo_ha_mute)?.setOnClickListener { exoToggleMute() }
} }
webView.externalBus( webView.externalBus(
id = json.get("id"), id = json.get("id"),
@ -932,20 +927,10 @@ class WebViewActivity : BaseActivity(), io.homeassistant.companion.android.webvi
exoMute = !exoMute exoMute = !exoMute
if (exoMute) { if (exoMute) {
exoPlayer?.volume = 0f exoPlayer?.volume = 0f
findViewById<ImageView>(R.id.exo_mute_icon).setImageDrawable( findViewById<ImageButton>(R.id.exo_ha_mute)?.setImageResource(R.drawable.ic_baseline_volume_off_24)
ContextCompat.getDrawable(
applicationContext,
R.drawable.ic_baseline_volume_off_24
)
)
} else { } else {
exoPlayer?.volume = 1f exoPlayer?.volume = 1f
findViewById<ImageView>(R.id.exo_mute_icon).setImageDrawable( findViewById<ImageButton>(R.id.exo_ha_mute)?.setImageResource(R.drawable.ic_baseline_volume_up_24)
ContextCompat.getDrawable(
applicationContext,
R.drawable.ic_baseline_volume_up_24
)
)
} }
} }
@ -953,22 +938,16 @@ class WebViewActivity : BaseActivity(), io.homeassistant.companion.android.webvi
val exoLayoutParams = exoPlayerView.layoutParams as FrameLayout.LayoutParams val exoLayoutParams = exoPlayerView.layoutParams as FrameLayout.LayoutParams
if (isExoFullScreen) { if (isExoFullScreen) {
if (resources.configuration.orientation == Configuration.ORIENTATION_LANDSCAPE) { if (resources.configuration.orientation == Configuration.ORIENTATION_LANDSCAPE) {
playerBinding.exoContentFrame.resizeMode = AspectRatioFrameLayout.RESIZE_MODE_FILL exoPlayerView.resizeMode = AspectRatioFrameLayout.RESIZE_MODE_FILL
} else { } else {
playerBinding.exoContentFrame.resizeMode = AspectRatioFrameLayout.RESIZE_MODE_FIXED_WIDTH exoPlayerView.resizeMode = AspectRatioFrameLayout.RESIZE_MODE_FIXED_WIDTH
} }
exoLayoutParams.setMargins(0, 0, 0, 0) exoLayoutParams.setMargins(0, 0, 0, 0)
exoPlayerView.layoutParams.height = FrameLayout.LayoutParams.MATCH_PARENT exoPlayerView.layoutParams.height = FrameLayout.LayoutParams.MATCH_PARENT
exoPlayerView.layoutParams.width = FrameLayout.LayoutParams.MATCH_PARENT exoPlayerView.layoutParams.width = FrameLayout.LayoutParams.MATCH_PARENT
findViewById<ImageView>(R.id.exo_fullscreen_icon).setImageDrawable(
ContextCompat.getDrawable(
applicationContext,
R.drawable.ic_baseline_fullscreen_exit_24
)
)
hideSystemUI() hideSystemUI()
} else { } else {
playerBinding.exoContentFrame.resizeMode = AspectRatioFrameLayout.RESIZE_MODE_FILL exoPlayerView.resizeMode = AspectRatioFrameLayout.RESIZE_MODE_FILL
exoPlayerView.layoutParams.height = FrameLayout.LayoutParams.WRAP_CONTENT exoPlayerView.layoutParams.height = FrameLayout.LayoutParams.WRAP_CONTENT
exoPlayerView.layoutParams.width = FrameLayout.LayoutParams.MATCH_PARENT exoPlayerView.layoutParams.width = FrameLayout.LayoutParams.MATCH_PARENT
val screenWidth: Int = resources.displayMetrics.widthPixels val screenWidth: Int = resources.displayMetrics.widthPixels
@ -979,12 +958,6 @@ class WebViewActivity : BaseActivity(), io.homeassistant.companion.android.webvi
maxOf(screenWidth - exoRight, 0), maxOf(screenWidth - exoRight, 0),
maxOf(screenHeight - exoBottom, 0) maxOf(screenHeight - exoBottom, 0)
) )
findViewById<ImageView>(R.id.exo_fullscreen_icon).setImageDrawable(
ContextCompat.getDrawable(
applicationContext,
R.drawable.ic_baseline_fullscreen_24
)
)
showSystemUI() showSystemUI()
} }
exoPlayerView.requestLayout() exoPlayerView.requestLayout()

View file

@ -1,10 +0,0 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24"
android:viewportHeight="24"
android:tint="?attr/colorControlNormal">
<path
android:fillColor="@android:color/white"
android:pathData="M7,14L5,14v5h5v-2L7,17v-3zM5,10h2L7,7h3L10,5L5,5v5zM17,17h-3v2h5v-5h-2v3zM14,5v2h3v3h2L19,5h-5z"/>
</vector>

View file

@ -1,10 +0,0 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24"
android:viewportHeight="24"
android:tint="?attr/colorControlNormal">
<path
android:fillColor="@android:color/white"
android:pathData="M5,16h3v3h2v-5L5,14v2zM8,8L5,8v2h5L10,5L8,5v3zM14,19h2v-3h3v-2h-5v5zM16,8L16,5h-2v5h5L19,8h-3z"/>
</vector>

View file

@ -2,8 +2,7 @@
android:width="24dp" android:width="24dp"
android:height="24dp" android:height="24dp"
android:viewportWidth="24" android:viewportWidth="24"
android:viewportHeight="24" android:viewportHeight="24">
android:tint="?attr/colorControlNormal">
<path <path
android:fillColor="@android:color/white" android:fillColor="@android:color/white"
android:pathData="M16.5,12c0,-1.77 -1.02,-3.29 -2.5,-4.03v2.21l2.45,2.45c0.03,-0.2 0.05,-0.41 0.05,-0.63zM19,12c0,0.94 -0.2,1.82 -0.54,2.64l1.51,1.51C20.63,14.91 21,13.5 21,12c0,-4.28 -2.99,-7.86 -7,-8.77v2.06c2.89,0.86 5,3.54 5,6.71zM4.27,3L3,4.27 7.73,9L3,9v6h4l5,5v-6.73l4.25,4.25c-0.67,0.52 -1.42,0.93 -2.25,1.18v2.06c1.38,-0.31 2.63,-0.95 3.69,-1.81L19.73,21 21,19.73l-9,-9L4.27,3zM12,4L9.91,6.09 12,8.18L12,4z"/> android:pathData="M16.5,12c0,-1.77 -1.02,-3.29 -2.5,-4.03v2.21l2.45,2.45c0.03,-0.2 0.05,-0.41 0.05,-0.63zM19,12c0,0.94 -0.2,1.82 -0.54,2.64l1.51,1.51C20.63,14.91 21,13.5 21,12c0,-4.28 -2.99,-7.86 -7,-8.77v2.06c2.89,0.86 5,3.54 5,6.71zM4.27,3L3,4.27 7.73,9L3,9v6h4l5,5v-6.73l4.25,4.25c-0.67,0.52 -1.42,0.93 -2.25,1.18v2.06c1.38,-0.31 2.63,-0.95 3.69,-1.81L19.73,21 21,19.73l-9,-9L4.27,3zM12,4L9.91,6.09 12,8.18L12,4z"/>

View file

@ -2,8 +2,7 @@
android:width="24dp" android:width="24dp"
android:height="24dp" android:height="24dp"
android:viewportWidth="24" android:viewportWidth="24"
android:viewportHeight="24" android:viewportHeight="24">
android:tint="?attr/colorControlNormal">
<path <path
android:fillColor="@android:color/white" android:fillColor="@android:color/white"
android:pathData="M3,9v6h4l5,5L12,4L7,9L3,9zM16.5,12c0,-1.77 -1.02,-3.29 -2.5,-4.03v8.05c1.48,-0.73 2.5,-2.25 2.5,-4.02zM14,3.23v2.06c2.89,0.86 5,3.54 5,6.71s-2.11,5.85 -5,6.71v2.06c4.01,-0.91 7,-4.49 7,-8.77s-2.99,-7.86 -7,-8.77z"/> android:pathData="M3,9v6h4l5,5L12,4L7,9L3,9zM16.5,12c0,-1.77 -1.02,-3.29 -2.5,-4.03v8.05c1.48,-0.73 2.5,-2.25 2.5,-4.02zM14,3.23v2.06c2.89,0.86 5,3.54 5,6.71s-2.11,5.85 -5,6.71v2.06c4.01,-0.91 7,-4.49 7,-8.77s-2.99,-7.86 -7,-8.77z"/>

View file

@ -20,11 +20,12 @@
android:id="@+id/exoviewGroup" android:id="@+id/exoviewGroup"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent"> android:layout_height="match_parent">
<com.google.android.exoplayer2.ui.PlayerView <androidx.media3.ui.PlayerView
android:id="@+id/exoplayerView" android:id="@+id/exoplayerView"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content"> android:layout_height="wrap_content"
</com.google.android.exoplayer2.ui.PlayerView> app:show_buffering="always">
</androidx.media3.ui.PlayerView>
</FrameLayout> </FrameLayout>
</RelativeLayout> </RelativeLayout>

View file

@ -1,67 +1,136 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android" <!-- Media3 doesn't allow customizing the default player controls, but you can override the layout
xmlns:tools="http://schemas.android.com/tools" file and it will pick up this instead of the library layout. Adjusted from the original layout
android:layout_width="match_parent" https://github.com/androidx/media/blob/a94aa8dbd99fc5ddec6ef25bb4c8ad7b3ca39e6f/libraries/ui/src/main/res/layout/exo_player_control_view.xml
android:layout_height="match_parent"> to fit the camera livestream use case / previous (ExoPlayer v2) layout by Home Assistant.
<LinearLayout android:id="@+id/exo_play_pause_button" -->
<!-- Copyright 2020 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.
-->
<merge xmlns:android="http://schemas.android.com/apk/res/android">
<View android:id="@id/exo_controls_background"
android:layout_width="0dp"
android:layout_height="0dp"
android:background="@color/exo_black_opacity_60"/>
<FrameLayout android:id="@id/exo_bottom_bar"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent" android:layout_height="@dimen/exo_styled_bottom_bar_height"
android:gravity="center" android:layout_marginTop="@dimen/exo_styled_bottom_bar_margin_top"
android:orientation="horizontal"> android:layout_gravity="bottom"
<ImageButton android:id="@+id/exo_play" android:background="@color/exo_bottom_bar_background"
android:layout_height="24dp" android:layout_width="24dp" android:layoutDirection="ltr">
style="@style/ExoMediaButton.Play"/>
<ImageButton android:id="@+id/exo_pause" <LinearLayout android:id="@id/exo_time"
android:layout_height="24dp" android:layout_width="24dp" android:layout_width="wrap_content"
style="@style/ExoMediaButton.Pause"/> android:layout_height="wrap_content"
android:paddingStart="@dimen/exo_styled_bottom_bar_time_padding"
android:paddingEnd="@dimen/exo_styled_bottom_bar_time_padding"
android:paddingLeft="@dimen/exo_styled_bottom_bar_time_padding"
android:paddingRight="@dimen/exo_styled_bottom_bar_time_padding"
android:layout_gravity="center_vertical|start"
android:layoutDirection="ltr">
<TextView android:id="@id/exo_position"
style="@style/ExoStyledControls.TimeText.Position"/>
<TextView
style="@style/ExoStyledControls.TimeText.Separator"/>
<TextView android:id="@id/exo_duration"
style="@style/ExoStyledControls.TimeText.Duration"/>
</LinearLayout>
<LinearLayout android:id="@id/exo_basic_controls"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_vertical|end"
android:layoutDirection="ltr">
<!-- HA: removed VR/shuffle/repeat/CC/settings buttons, added mute button -->
<ImageButton android:id="@+id/exo_ha_mute"
style="@style/ExoStyledControls.Button.Bottom.HaMute"/>
<ImageButton android:id="@id/exo_fullscreen"
style="@style/ExoStyledControls.Button.Bottom.FullScreen"/>
<ImageButton android:id="@id/exo_overflow_show"
style="@style/ExoStyledControls.Button.Bottom.OverflowShow"/>
</LinearLayout>
<HorizontalScrollView android:id="@id/exo_extra_controls_scroll_view"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_vertical|end"
android:visibility="invisible">
<LinearLayout android:id="@id/exo_extra_controls"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layoutDirection="ltr">
<ImageButton android:id="@id/exo_overflow_hide"
style="@style/ExoStyledControls.Button.Bottom.OverflowHide"/>
</LinearLayout>
</HorizontalScrollView>
</FrameLayout>
<View android:id="@id/exo_progress_placeholder"
android:layout_width="match_parent"
android:layout_height="@dimen/exo_styled_progress_layout_height"
android:layout_gravity="bottom"
android:layout_marginBottom="@dimen/exo_styled_progress_margin_bottom"/>
<LinearLayout android:id="@id/exo_minimal_controls"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="bottom|end"
android:layout_marginBottom="@dimen/exo_styled_minimal_controls_margin_bottom"
android:orientation="horizontal"
android:gravity="center_vertical"
android:layoutDirection="ltr">
<ImageButton android:id="@id/exo_minimal_fullscreen"
style="@style/ExoStyledControls.Button.Bottom.FullScreen"/>
</LinearLayout> </LinearLayout>
<LinearLayout <LinearLayout
android:layout_width="match_parent" android:id="@id/exo_center_controls"
android:layout_height="match_parent" android:layout_width="wrap_content"
android:layout_marginTop="4dp" android:layout_height="wrap_content"
android:gravity="bottom" android:layout_gravity="center"
android:orientation="horizontal"> android:background="@android:color/transparent"
<TextView android:id="@+id/exo_position" android:gravity="center"
android:layout_width="wrap_content" android:padding="@dimen/exo_styled_controls_padding"
android:layout_height="wrap_content" android:clipToPadding="false"
android:textSize="14sp" android:layoutDirection="ltr">
android:textStyle="bold"
android:paddingLeft="4dp" <!-- HA: removed prev/rewind -->
android:paddingRight="4dp"
android:includeFontPadding="false" <ImageButton android:id="@id/exo_play_pause"
android:textColor="#FFBEBEBE"/> style="@style/ExoStyledControls.Button.Center.PlayPause"/>
<View android:id="@+id/exo_progress_placeholder"
android:layout_width="0dp" <!-- HA: removed ffwd/next -->
android:layout_weight="1"
android:layout_height="24dp"/>
<TextView android:id="@+id/exo_duration"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textSize="14sp"
android:textStyle="bold"
android:paddingLeft="4dp"
android:paddingRight="4dp"
android:includeFontPadding="false"
android:textColor="#FFBEBEBE"/>
<Space
android:layout_width="16dp"
android:layout_height="4dp"/>
<ImageView
android:id="@+id/exo_mute_icon"
android:layout_width="24dp"
android:layout_height="24dp"
android:adjustViewBounds="true"
android:scaleType="fitCenter"
android:src="@drawable/ic_baseline_volume_up_24"/>
<Space
android:layout_width="16dp"
android:layout_height="4dp"/>
<ImageView
android:id="@+id/exo_fullscreen_icon"
android:layout_width="24dp"
android:layout_height="24dp"
android:adjustViewBounds="true"
android:scaleType="fitCenter"
android:src="@drawable/ic_baseline_fullscreen_24"/>
</LinearLayout> </LinearLayout>
</FrameLayout>
</merge>

View file

@ -1,23 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="#00000000">
<com.google.android.exoplayer2.ui.AspectRatioFrameLayout android:id="@+id/exo_content_frame"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_gravity="center">
<ProgressBar
android:id="@+id/exo_buffering"
android:layout_width="48dp"
android:layout_height="48dp"
android:layout_gravity="center"
android:indeterminate="true"
/>
</com.google.android.exoplayer2.ui.AspectRatioFrameLayout>
<com.google.android.exoplayer2.ui.PlayerControlView android:id="@+id/exo_controller"
android:layout_height="match_parent"
android:layout_width="match_parent">
</com.google.android.exoplayer2.ui.PlayerControlView>
</FrameLayout>

View file

@ -131,4 +131,9 @@
<item name="android:windowIsFloating">true</item> <item name="android:windowIsFloating">true</item>
<item name="android:backgroundDimEnabled">false</item> <item name="android:backgroundDimEnabled">false</item>
</style> </style>
<style name="ExoStyledControls.Button.Bottom.HaMute">
<item name="android:src">@drawable/ic_baseline_volume_up_24</item>
<item name="android:contentDescription">@string/mute_unmute</item>
</style>
</resources> </resources>

View file

@ -188,11 +188,9 @@ dependencies {
implementation(libs.biometric) implementation(libs.biometric)
implementation(libs.webkit) implementation(libs.webkit)
implementation(libs.exoplayer.core) implementation(libs.bundles.media3)
implementation(libs.exoplayer.hls) "fullImplementation"(libs.media3.datasource.cronet)
implementation(libs.exoplayer.ui) "minimalImplementation"(libs.media3.datasource.cronet) {
"fullImplementation"(libs.extension.cronet)
"minimalImplementation"(libs.extension.cronet) {
exclude(group = "com.google.android.gms", module = "play-services-cronet") exclude(group = "com.google.android.gms", module = "play-services-cronet")
} }
"minimalImplementation"(libs.cronet.embedded) "minimalImplementation"(libs.cronet.embedded)

View file

@ -397,6 +397,7 @@
<string name="message_no_connected_nodes">No connected Wear devices, please make sure Bluetooth is on and your watch is paired.</string> <string name="message_no_connected_nodes">No connected Wear devices, please make sure Bluetooth is on and your watch is paired.</string>
<string name="message_some_installed">The Wear app is installed on some of your Wear devices: (%1$s)\n\nClick the button below to install the app on the other devices.</string> <string name="message_some_installed">The Wear app is installed on some of your Wear devices: (%1$s)\n\nClick the button below to install the app on the other devices.</string>
<string name="missing_command_permission">Please open the Home Assistant app and send the command again in order to grant the proper permissions. You will be taken to a page to either grant the Home Assistant app the permission, or you will need to select Permissions from the details page and then grant the missing permission. For command_bluetooth the name of the permission is Nearby devices. If you are attempting to use command_activity to make a phone call you will also need to grant Phone permissions.</string> <string name="missing_command_permission">Please open the Home Assistant app and send the command again in order to grant the proper permissions. You will be taken to a page to either grant the Home Assistant app the permission, or you will need to select Permissions from the details page and then grant the missing permission. For command_bluetooth the name of the permission is Nearby devices. If you are attempting to use command_activity to make a phone call you will also need to grant Phone permissions.</string>
<string name="mute_unmute">Mute/Unmute</string>
<string name="areas">Areas</string> <string name="areas">Areas</string>
<string name="more_entities">More entities</string> <string name="more_entities">More entities</string>
<string name="need_help">Need Help?</string> <string name="need_help">Need Help?</string>

View file

@ -23,7 +23,6 @@ converterJackson = "2.9.0"
coreKtx = "1.10.1" coreKtx = "1.10.1"
cronet-embedded = "113.5672.61" cronet-embedded = "113.5672.61"
emojiJava = "5.1.1" emojiJava = "5.1.1"
exoplayer = "2.19.0"
firebase-bom = "32.2.0" firebase-bom = "32.2.0"
firebaseAppdistributionGradle = "4.0.0" firebaseAppdistributionGradle = "4.0.0"
fragment-ktx = "1.6.1" fragment-ktx = "1.6.1"
@ -40,6 +39,7 @@ ktlint = "11.5.0"
lifecycle = "2.6.1" lifecycle = "2.6.1"
loggingInterceptor = "4.11.0" loggingInterceptor = "4.11.0"
material = "1.9.0" material = "1.9.0"
media3 = "1.1.0"
navigation-compose = "2.6.0" navigation-compose = "2.6.0"
okhttp = "4.11.0" okhttp = "4.11.0"
picasso = "2.8" picasso = "2.8"
@ -110,10 +110,6 @@ converter-jackson = { module = "com.squareup.retrofit2:converter-jackson", versi
core-ktx = { module = "androidx.core:core-ktx", version.ref = "coreKtx" } core-ktx = { module = "androidx.core:core-ktx", version.ref = "coreKtx" }
cronet-embedded = { module = "org.chromium.net:cronet-embedded", version.ref = "cronet-embedded" } cronet-embedded = { module = "org.chromium.net:cronet-embedded", version.ref = "cronet-embedded" }
emojiJava = { module = "com.vdurmont:emoji-java", version.ref = "emojiJava" } emojiJava = { module = "com.vdurmont:emoji-java", version.ref = "emojiJava" }
extension-cronet = { module = "com.google.android.exoplayer:extension-cronet", version.ref = "exoplayer" }
exoplayer-ui = { module = "com.google.android.exoplayer:exoplayer-ui", version.ref = "exoplayer" }
exoplayer-hls = { module = "com.google.android.exoplayer:exoplayer-hls", version.ref = "exoplayer" }
exoplayer-core = { module = "com.google.android.exoplayer:exoplayer-core", version.ref = "exoplayer" }
firebase-bom = { module = "com.google.firebase:firebase-bom", version.ref = "firebase-bom" } firebase-bom = { module = "com.google.firebase:firebase-bom", version.ref = "firebase-bom" }
firebase-messaging = { module = "com.google.firebase:firebase-messaging" } firebase-messaging = { module = "com.google.firebase:firebase-messaging" }
fragment-ktx = { module = "androidx.fragment:fragment-ktx", version.ref = "fragment-ktx" } fragment-ktx = { module = "androidx.fragment:fragment-ktx", version.ref = "fragment-ktx" }
@ -134,6 +130,10 @@ logging-interceptor = { module = "com.squareup.okhttp3:logging-interceptor", ver
navigation-compose = { module = "androidx.navigation:navigation-compose", version.ref = "navigation-compose" } navigation-compose = { module = "androidx.navigation:navigation-compose", version.ref = "navigation-compose" }
okhttp = { module = "com.squareup.okhttp3:okhttp", version.ref = "okhttp" } okhttp = { module = "com.squareup.okhttp3:okhttp", version.ref = "okhttp" }
material = { module = "com.google.android.material:material", version.ref = "material" } material = { module = "com.google.android.material:material", version.ref = "material" }
media3-datasource-cronet = { module = "androidx.media3:media3-datasource-cronet", version.ref = "media3" }
media3-exoplayer = { module = "androidx.media3:media3-exoplayer", version.ref = "media3" }
media3-exoplayer-hls = { module = "androidx.media3:media3-exoplayer-hls", version.ref = "media3" }
media3-ui = { module = "androidx.media3:media3-ui", version.ref = "media3" }
play-services-threadnetwork = { module = "com.google.android.gms:play-services-threadnetwork", version.ref = "play-services-threadnetwork" } play-services-threadnetwork = { module = "com.google.android.gms:play-services-threadnetwork", version.ref = "play-services-threadnetwork" }
play-services-home = { module = "com.google.android.gms:play-services-home", version.ref = "play-services-home" } play-services-home = { module = "com.google.android.gms:play-services-home", version.ref = "play-services-home" }
play-services-location = { module = "com.google.android.gms:play-services-location", version.ref = "play-services-location" } play-services-location = { module = "com.google.android.gms:play-services-location", version.ref = "play-services-location" }
@ -154,3 +154,6 @@ wear-remote-interactions = { module = "androidx.wear:wear-remote-interactions",
wear-tiles-material = { module = "androidx.wear.tiles:tiles-material", version.ref = "wear-tiles" } wear-tiles-material = { module = "androidx.wear.tiles:tiles-material", version.ref = "wear-tiles" }
wear-tiles = { module = "androidx.wear.tiles:tiles", version.ref = "wear-tiles" } wear-tiles = { module = "androidx.wear.tiles:tiles", version.ref = "wear-tiles" }
webkit = { module = "androidx.webkit:webkit", version.ref = "webkit" } webkit = { module = "androidx.webkit:webkit", version.ref = "webkit" }
[bundles]
media3 = ["media3-exoplayer", "media3-exoplayer-hls", "media3-ui"]