Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,7 @@

## stream-chat-android-ui-components
### 🐞 Fixed
- Fix ExoPlayer crash when playing a video, happening when the integration app is using the legacy `com.google.android.exoplayer` library. [#6013](https://github.com/GetStream/stream-chat-android/pull/6013)

### ⬆️ Improved

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -147,10 +147,14 @@ public class AttachmentMediaActivity : AppCompatActivity() {
binding.controls.show()
}
}
controllerHideOnTouch = true
setControllerHideOnTouch(true)
setShowBuffering(PlayerView.SHOW_BUFFERING_NEVER)
artworkDisplayMode = PlayerView.ARTWORK_DISPLAY_MODE_OFF
setArtworkDisplayMode(PlayerView.ARTWORK_DISPLAY_MODE_OFF)
// Disable default controller because we use the legacy one
setUseController(false)
}

// Setup legacy controller
binding.controls.player = player
binding.controls.showTimeoutMs = CONTROLLER_SHOW_TIMEOUT
binding.controls.setShowNextButton(false)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -195,9 +195,9 @@ internal class AttachmentGalleryVideoPageFragment : Fragment() {
private fun setupPlayerView(player: Player) {
binding.playerView.apply {
this.player = player
controllerShowTimeoutMs = CONTROLLER_SHOW_TIMEOUT
controllerAutoShow = false
controllerHideOnTouch = true
setControllerShowTimeoutMs(CONTROLLER_SHOW_TIMEOUT)
setControllerAutoShow(false)
setControllerHideOnTouch(true)
setShowPreviousButton(false)
setShowNextButton(false)
setShowBuffering(PlayerView.SHOW_BUFFERING_NEVER)
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,161 @@
/*
* Copyright (c) 2014-2025 Stream.io Inc. All rights reserved.
*
* Licensed under the Stream License;
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://github.com/GetStream/stream-chat-android/blob/main/LICENSE
*
* 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 io.getstream.chat.android.ui.widgets.internal

import android.content.Context
import android.util.AttributeSet
import android.view.LayoutInflater
import android.widget.FrameLayout
import androidx.annotation.OptIn
import androidx.media3.common.Player
import androidx.media3.common.util.UnstableApi
import androidx.media3.ui.PlayerView
import io.getstream.chat.android.core.internal.StreamHandsOff
import io.getstream.chat.android.ui.R

/**
* A custom view that wraps Media3's [androidx.media3.ui.PlayerView] with automatic inflation handling.
*
* This view automatically inflates the PlayerView layout with custom root and controller layouts
* to avoid conflicts with different versions of the ExoPlayer library in integration projects.
*
* Usage:
* ```xml
* <io.getstream.chat.android.ui.widgets.internal.StreamPlayerView
* android:id="@+id/streamPlayerView"
* android:layout_width="match_parent"
* android:layout_height="match_parent" />
* ```
*
* Then in code:
* ```kotlin
* streamPlayerView.player = exoPlayer
* streamPlayerView.setShowBuffering(PlayerView.SHOW_BUFFERING_NEVER)
* ```
*/

@OptIn(UnstableApi::class)
internal class StreamPlayerView @JvmOverloads constructor(
context: Context,
attrs: AttributeSet? = null,
defStyleAttr: Int = 0,
) : FrameLayout(context, attrs, defStyleAttr) {

private val playerView: PlayerView

/**
* Gets or sets the [androidx.media3.common.Player] instance.
*/
var player: Player?
get() = playerView.player
set(value) {
playerView.player = value
}

init {
playerView = inflatePlayerView()
addView(playerView)
}

@StreamHandsOff(
"This method manually inflates the PlayerView layout, because we are overriding the default " +
"root layout (player_layout_id) and the default controller layout (controller_layout_id). " +
"These layouts are just copies of the 'exo_player_view.xml' and the 'exo_player_control_view.xml' from " +
"the ExoPlayer library. They only have a different name (layout ID) to avoid being overridden by layouts " +
"with the same name from different versions of the ExoPlayer library (included in integration projects). " +
"This ensures that we always use the correct layout for our version of the ExoPlayer library",
)
private fun inflatePlayerView(): PlayerView {
return LayoutInflater
.from(context)
.inflate(R.layout.stream_ui_player_view, this, false) as PlayerView
}

/**
* Sets whether buffering should be shown.
*
* @param showBuffering One of [PlayerView.SHOW_BUFFERING_NEVER],
* [PlayerView.SHOW_BUFFERING_WHEN_PLAYING], or [PlayerView.SHOW_BUFFERING_ALWAYS].
*/
fun setShowBuffering(showBuffering: Int) {
playerView.setShowBuffering(showBuffering)
}

/**
* Sets the artwork display mode.
*
* @param artworkDisplayMode One of [PlayerView.ARTWORK_DISPLAY_MODE_OFF],
* [PlayerView.ARTWORK_DISPLAY_MODE_FIT], or [PlayerView.ARTWORK_DISPLAY_MODE_FILL].
*/
fun setArtworkDisplayMode(artworkDisplayMode: Int) {
playerView.artworkDisplayMode = artworkDisplayMode
}

/**
* Sets whether the controller should be shown.
*
* @param useController Whether to use the controller.
*/
fun setUseController(useController: Boolean) {
playerView.useController = useController
}

/**
* Sets whether the controller should be shown automatically.
*
* @param autoShow Whether to show the controller automatically.
*/
fun setControllerAutoShow(autoShow: Boolean) {
playerView.controllerAutoShow = autoShow
}

/**
* Sets the controller show timeout.
*
* @param timeout the timeout in milliseconds.
*/
fun setControllerShowTimeoutMs(timeout: Int) {
playerView.controllerShowTimeoutMs = timeout
}

/**
* Sets whether the controller should hide on touch.
*
* @param controllerHideOnTouch Whether the controller should hide on touch.
*/
fun setControllerHideOnTouch(controllerHideOnTouch: Boolean) {
playerView.controllerHideOnTouch = controllerHideOnTouch
}

/**
* Sets whether the "Previous" button shown be shown.
*
* @param show Whether the "Previous" button should be shown.
*/
fun setShowPreviousButton(show: Boolean) {
playerView.setShowPreviousButton(show)
}

/**
* Sets whether the "Next" button shown be shown.
*
* @param show Whether the "Next" button should be shown.
*/
fun setShowNextButton(show: Boolean) {
playerView.setShowNextButton(show)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -76,21 +76,23 @@
>

<LinearLayout
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="match_parent" >
<androidx.media3.ui.PlayerView
android:layout_height="match_parent"
android:orientation="vertical"
>

<io.getstream.chat.android.ui.widgets.internal.StreamPlayerView
android:id="@+id/playerView"
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="1"
app:use_controller="false"
/>

<androidx.media3.ui.LegacyPlayerControlView
android:id="@+id/controls"
android:layout_width="match_parent"
android:layout_height="wrap_content" />
android:layout_height="wrap_content"
/>
</LinearLayout>

<ImageView
Expand Down
Loading
Loading