Compare commits
8 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
8350fae372 | ||
|
|
0912212887 | ||
|
|
f7839968b2 | ||
|
|
3ca8667d32 | ||
|
|
af782baf86 | ||
|
|
920ddccc80 | ||
|
|
71308f6ddc | ||
|
|
db22ecd787 |
@@ -14,6 +14,7 @@
|
||||
- [mohd-akram](https://github.com/mohd-akram)
|
||||
- [3l0w](https://github.com/3l0w)
|
||||
- [MajMongoose](https://github.com/majmongoose)
|
||||
- [Olaren15](https://github.com/Olaren15)
|
||||
|
||||
# Emby Contributors
|
||||
|
||||
|
||||
@@ -17,6 +17,7 @@ import androidx.compose.foundation.layout.size
|
||||
import androidx.compose.foundation.layout.widthIn
|
||||
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.LaunchedEffect
|
||||
import androidx.compose.runtime.collectAsState
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.ui.Alignment
|
||||
@@ -50,7 +51,9 @@ import org.jellyfin.sdk.model.api.ImageType
|
||||
import org.koin.compose.koinInject
|
||||
|
||||
@Composable
|
||||
fun NowPlayingComposable() {
|
||||
fun NowPlayingComposable(
|
||||
onFocusableChange: (focusable: Boolean) -> Unit,
|
||||
) {
|
||||
val playbackManager = koinInject<PlaybackManager>()
|
||||
val navigationRepository = koinInject<NavigationRepository>()
|
||||
val imageHelper = koinInject<ImageHelper>()
|
||||
@@ -59,6 +62,8 @@ fun NowPlayingComposable() {
|
||||
val item = entry?.run { baseItemFlow.collectAsState(baseItem) }?.value
|
||||
val progress = rememberPlayerProgress(playbackManager)
|
||||
|
||||
LaunchedEffect(item == null) { onFocusableChange(item != null) }
|
||||
|
||||
AnimatedVisibility(
|
||||
visible = item != null,
|
||||
enter = fadeIn(),
|
||||
@@ -153,5 +158,11 @@ class NowPlayingView @JvmOverloads constructor(
|
||||
defStyle: Int = 0
|
||||
) : AbstractComposeView(context, attrs, defStyle) {
|
||||
@Composable
|
||||
override fun Content() = NowPlayingComposable()
|
||||
override fun Content() = NowPlayingComposable(
|
||||
// Workaround for older Android versions unable to find focus in our toolbar view when the NowPlayingView is added but inactive
|
||||
onFocusableChange = { focusable ->
|
||||
isFocusable = focusable
|
||||
descendantFocusability = if (focusable) FOCUS_AFTER_DESCENDANTS else FOCUS_BLOCK_DESCENDANTS
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
@@ -235,7 +235,9 @@ public class LiveTvGuideFragment extends Fragment implements LiveTvGuide, View.O
|
||||
|
||||
public void refreshFavorite(UUID channelId){
|
||||
for (int i = 0; i < mChannels.getChildCount(); i++) {
|
||||
GuideChannelHeader gch = (GuideChannelHeader)mChannels.getChildAt(i);
|
||||
View child = mChannels.getChildAt(i);
|
||||
if (!(child instanceof GuideChannelHeader)) continue;
|
||||
GuideChannelHeader gch = (GuideChannelHeader) child;
|
||||
if (gch.getChannel().getId().equals(channelId.toString()))
|
||||
gch.refreshFavorite();
|
||||
}
|
||||
|
||||
@@ -512,7 +512,7 @@ public class PlaybackController implements PlaybackControllerNotifiable {
|
||||
internalOptions.setMediaSourceId(currentMediaSource.getId());
|
||||
}
|
||||
DeviceProfile internalProfile = new ExoPlayerProfile(
|
||||
isLiveTv && !userPreferences.getValue().get(UserPreferences.Companion.getLiveTvDirectPlayEnabled()),
|
||||
!internalOptions.getEnableDirectStream(),
|
||||
userPreferences.getValue().get(UserPreferences.Companion.getAc3Enabled()),
|
||||
userPreferences.getValue().get(UserPreferences.Companion.getAudioBehaviour()) == AudioBehavior.DOWNMIX_TO_STEREO
|
||||
);
|
||||
@@ -901,6 +901,8 @@ public class PlaybackController implements PlaybackControllerNotifiable {
|
||||
// set seekPosition so real position isn't used until playback starts again
|
||||
mSeekPosition = pos;
|
||||
|
||||
if (mCurrentStreamInfo == null) return;
|
||||
|
||||
// rebuild the stream
|
||||
// if an older device uses exoplayer to play a transcoded stream but falls back to the generic http stream instead of hls, rebuild the stream
|
||||
if (!mVideoManager.isSeekable()) {
|
||||
|
||||
@@ -109,6 +109,13 @@ public class CustomPlaybackTransportControlGlue extends PlaybackTransportControl
|
||||
protected void onDetachedFromHost() {
|
||||
mHandler.removeCallbacks(mRefreshEndTime);
|
||||
mHandler.removeCallbacks(mRefreshViewVisibility);
|
||||
|
||||
closedCaptionsAction.removePopup();
|
||||
playbackSpeedAction.dismissPopup();
|
||||
selectAudioAction.dismissPopup();
|
||||
selectQualityAction.dismissPopup();
|
||||
zoomAction.dismissPopup();
|
||||
|
||||
super.onDetachedFromHost();
|
||||
}
|
||||
|
||||
@@ -169,6 +176,7 @@ public class CustomPlaybackTransportControlGlue extends PlaybackTransportControl
|
||||
super.onBindRowViewHolder(vh, item);
|
||||
vh.setOnKeyListener(CustomPlaybackTransportControlGlue.this);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onUnbindRowViewHolder(RowPresenter.ViewHolder vh) {
|
||||
super.onUnbindRowViewHolder(vh);
|
||||
|
||||
@@ -17,6 +17,8 @@ class ClosedCaptionsAction(
|
||||
context: Context,
|
||||
customPlaybackTransportControlGlue: CustomPlaybackTransportControlGlue,
|
||||
) : CustomAction(context, customPlaybackTransportControlGlue) {
|
||||
private var popup: PopupMenu? = null
|
||||
|
||||
init {
|
||||
initializeWithIcon(R.drawable.ic_select_subtitle)
|
||||
}
|
||||
@@ -34,7 +36,8 @@ class ClosedCaptionsAction(
|
||||
}
|
||||
|
||||
videoPlayerAdapter.leanbackOverlayFragment.setFading(false)
|
||||
PopupMenu(context, view, Gravity.END).apply {
|
||||
removePopup()
|
||||
popup = PopupMenu(context, view, Gravity.END).apply {
|
||||
with(menu) {
|
||||
var order = 0
|
||||
add(0, -1, order++, context.getString(R.string.lbl_none)).apply {
|
||||
@@ -51,11 +54,19 @@ class ClosedCaptionsAction(
|
||||
|
||||
setGroupCheckable(0, true, false)
|
||||
}
|
||||
setOnDismissListener { videoPlayerAdapter.leanbackOverlayFragment.setFading(true) }
|
||||
setOnDismissListener {
|
||||
videoPlayerAdapter.leanbackOverlayFragment.setFading(true)
|
||||
popup = null
|
||||
}
|
||||
setOnMenuItemClickListener { item ->
|
||||
playbackController.setSubtitleIndex(item.itemId)
|
||||
true
|
||||
}
|
||||
}.show()
|
||||
}
|
||||
popup?.show()
|
||||
}
|
||||
|
||||
fun removePopup() {
|
||||
popup?.dismiss()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -18,6 +18,7 @@ class PlaybackSpeedAction(
|
||||
) : CustomAction(context, customPlaybackTransportControlGlue) {
|
||||
private val speedController = VideoSpeedController(playbackController)
|
||||
private val speeds = VideoSpeedController.SpeedSteps.entries.toTypedArray()
|
||||
private var popup: PopupMenu? = null
|
||||
|
||||
init {
|
||||
initializeWithIcon(R.drawable.ic_playback_speed)
|
||||
@@ -30,17 +31,20 @@ class PlaybackSpeedAction(
|
||||
view: View,
|
||||
) {
|
||||
videoPlayerAdapter.leanbackOverlayFragment.setFading(false)
|
||||
val speedMenu = populateMenu(context, view, speedController)
|
||||
dismissPopup()
|
||||
popup = populateMenu(context, view, speedController)
|
||||
|
||||
speedMenu.setOnDismissListener { videoPlayerAdapter.leanbackOverlayFragment.setFading(true) }
|
||||
popup?.setOnDismissListener {
|
||||
videoPlayerAdapter.leanbackOverlayFragment.setFading(true)
|
||||
popup = null
|
||||
}
|
||||
|
||||
speedMenu.setOnMenuItemClickListener { menuItem ->
|
||||
popup?.setOnMenuItemClickListener { menuItem ->
|
||||
speedController.currentSpeed = speeds[menuItem.itemId]
|
||||
speedMenu.dismiss()
|
||||
true
|
||||
}
|
||||
|
||||
speedMenu.show()
|
||||
popup?.show()
|
||||
}
|
||||
|
||||
private fun populateMenu(
|
||||
@@ -57,4 +61,7 @@ class PlaybackSpeedAction(
|
||||
menu.getItem(speeds.indexOf(speedController.currentSpeed)).isChecked = true
|
||||
}
|
||||
|
||||
fun dismissPopup() {
|
||||
popup?.dismiss()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,7 +8,6 @@ import org.jellyfin.androidtv.R
|
||||
import org.jellyfin.androidtv.ui.playback.PlaybackController
|
||||
import org.jellyfin.androidtv.ui.playback.PlaybackManager
|
||||
import org.jellyfin.androidtv.ui.playback.overlay.CustomPlaybackTransportControlGlue
|
||||
import org.jellyfin.androidtv.ui.playback.overlay.LeanbackOverlayFragment
|
||||
import org.jellyfin.androidtv.ui.playback.overlay.VideoPlayerAdapter
|
||||
|
||||
class SelectAudioAction(
|
||||
@@ -16,6 +15,8 @@ class SelectAudioAction(
|
||||
customPlaybackTransportControlGlue: CustomPlaybackTransportControlGlue,
|
||||
private val playbackManager: PlaybackManager,
|
||||
) : CustomAction(context, customPlaybackTransportControlGlue) {
|
||||
private var popup: PopupMenu? = null
|
||||
|
||||
init {
|
||||
initializeWithIcon(R.drawable.ic_select_audio)
|
||||
}
|
||||
@@ -31,7 +32,8 @@ class SelectAudioAction(
|
||||
?: return
|
||||
val currentAudioIndex = playbackController.audioStreamIndex
|
||||
|
||||
PopupMenu(context, view, Gravity.END).apply {
|
||||
dismissPopup()
|
||||
popup = PopupMenu(context, view, Gravity.END).apply {
|
||||
with(menu) {
|
||||
for (track in audioTracks) {
|
||||
add(0, track.index, track.index, track.displayTitle).apply {
|
||||
@@ -41,11 +43,19 @@ class SelectAudioAction(
|
||||
setGroupCheckable(0, true, false)
|
||||
}
|
||||
|
||||
setOnDismissListener { videoPlayerAdapter.leanbackOverlayFragment.setFading(true) }
|
||||
setOnDismissListener {
|
||||
videoPlayerAdapter.leanbackOverlayFragment.setFading(true)
|
||||
popup = null
|
||||
}
|
||||
setOnMenuItemClickListener { item ->
|
||||
playbackController.switchAudioStream(item.itemId)
|
||||
true
|
||||
}
|
||||
}.show()
|
||||
}
|
||||
popup?.show()
|
||||
}
|
||||
|
||||
fun dismissPopup() {
|
||||
popup?.dismiss()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -20,6 +20,7 @@ class SelectQualityAction(
|
||||
private val previousQualitySelection = userPreferences[UserPreferences.maxBitrate]
|
||||
private val qualityController = VideoQualityController(previousQualitySelection, userPreferences)
|
||||
private val qualityProfiles = getQualityProfiles(context)
|
||||
private var popup: PopupMenu? = null
|
||||
|
||||
init {
|
||||
initializeWithIcon(R.drawable.ic_select_quality)
|
||||
@@ -32,7 +33,8 @@ class SelectQualityAction(
|
||||
view: View,
|
||||
) {
|
||||
videoPlayerAdapter.leanbackOverlayFragment.setFading(false)
|
||||
PopupMenu(context, view, Gravity.END).apply {
|
||||
dismissPopup()
|
||||
popup = PopupMenu(context, view, Gravity.END).apply {
|
||||
qualityProfiles.values.forEachIndexed { i, selected ->
|
||||
menu.add(0, i, i, selected)
|
||||
}
|
||||
@@ -45,13 +47,21 @@ class SelectQualityAction(
|
||||
}
|
||||
}
|
||||
|
||||
setOnDismissListener { videoPlayerAdapter.leanbackOverlayFragment.setFading(true) }
|
||||
setOnDismissListener {
|
||||
videoPlayerAdapter.leanbackOverlayFragment.setFading(true)
|
||||
popup = null
|
||||
}
|
||||
setOnMenuItemClickListener { menuItem ->
|
||||
qualityController.currentQuality = qualityProfiles.keys.elementAt(menuItem.itemId)
|
||||
playbackController.refreshStream()
|
||||
dismiss()
|
||||
true
|
||||
}
|
||||
}.show()
|
||||
}
|
||||
popup?.show()
|
||||
}
|
||||
|
||||
fun dismissPopup() {
|
||||
popup?.dismiss()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,6 +3,7 @@ package org.jellyfin.androidtv.ui.playback.overlay.action
|
||||
import android.content.Context
|
||||
import android.view.Gravity
|
||||
import android.view.View
|
||||
import android.widget.PopupMenu
|
||||
import org.jellyfin.androidtv.R
|
||||
import org.jellyfin.androidtv.preference.constant.ZoomMode
|
||||
import org.jellyfin.androidtv.ui.playback.PlaybackController
|
||||
@@ -14,6 +15,8 @@ class ZoomAction(
|
||||
context: Context,
|
||||
customPlaybackTransportControlGlue: CustomPlaybackTransportControlGlue,
|
||||
) : CustomAction(context, customPlaybackTransportControlGlue) {
|
||||
private var popup: PopupMenu? = null
|
||||
|
||||
init {
|
||||
initializeWithIcon(R.drawable.ic_aspect_ratio)
|
||||
}
|
||||
@@ -25,7 +28,8 @@ class ZoomAction(
|
||||
view: View,
|
||||
) {
|
||||
videoPlayerAdapter.leanbackOverlayFragment.setFading(false)
|
||||
val popup = popupMenu(context, view, Gravity.END) {
|
||||
dismissPopup()
|
||||
popup = popupMenu(context, view, Gravity.END) {
|
||||
item(context.getString(R.string.lbl_fit)) {
|
||||
playbackController.setZoom(ZoomMode.FIT)
|
||||
}.apply {
|
||||
@@ -44,8 +48,15 @@ class ZoomAction(
|
||||
isChecked = playbackController.zoomMode == ZoomMode.STRETCH
|
||||
}
|
||||
}
|
||||
popup.menu.setGroupCheckable(0, true, true)
|
||||
popup.setOnDismissListener { videoPlayerAdapter.leanbackOverlayFragment.setFading(true) }
|
||||
popup.show()
|
||||
popup?.menu?.setGroupCheckable(0, true, true)
|
||||
popup?.setOnDismissListener {
|
||||
videoPlayerAdapter.leanbackOverlayFragment.setFading(true)
|
||||
popup = null
|
||||
}
|
||||
popup?.show()
|
||||
}
|
||||
|
||||
fun dismissPopup() {
|
||||
popup?.dismiss()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -70,7 +70,9 @@
|
||||
<androidx.compose.ui.platform.ComposeView
|
||||
android:id="@+id/notifications"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content" />
|
||||
android:layout_height="wrap_content"
|
||||
android:descendantFocusability="blocksDescendants"
|
||||
android:focusable="false" />
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
|
||||
@@ -21,6 +21,7 @@ import org.jellyfin.sdk.model.api.PlaybackStartInfo
|
||||
import org.jellyfin.sdk.model.api.PlaybackStopInfo
|
||||
import org.jellyfin.sdk.model.api.QueueItem
|
||||
import org.jellyfin.sdk.model.extensions.inWholeTicks
|
||||
import timber.log.Timber
|
||||
import kotlin.math.roundToInt
|
||||
import org.jellyfin.sdk.model.api.RepeatMode as SdkRepeatMode
|
||||
|
||||
@@ -70,27 +71,29 @@ class PlaySessionService(
|
||||
val stream = entry.mediaStream ?: return
|
||||
val item = entry.baseItem ?: return
|
||||
|
||||
api.playStateApi.reportPlaybackStart(
|
||||
PlaybackStartInfo(
|
||||
itemId = item.id,
|
||||
playSessionId = stream.identifier,
|
||||
playlistItemId = item.playlistItemId,
|
||||
canSeek = true,
|
||||
isMuted = state.volume.muted,
|
||||
volumeLevel = (state.volume.volume * 100).roundToInt(),
|
||||
isPaused = state.playState.value != PlayState.PLAYING,
|
||||
aspectRatio = state.videoSize.value.aspectRatio.toString(),
|
||||
positionTicks = withContext(Dispatchers.Main) { state.positionInfo.active.inWholeTicks },
|
||||
playMethod = stream.conversionMethod.playMethod,
|
||||
repeatMode = state.repeatMode.value.remoteRepeatMode,
|
||||
nowPlayingQueue = getQueue(),
|
||||
playbackOrder = when (state.playbackOrder.value) {
|
||||
org.jellyfin.playback.core.model.PlaybackOrder.DEFAULT -> PlaybackOrder.DEFAULT
|
||||
org.jellyfin.playback.core.model.PlaybackOrder.RANDOM -> PlaybackOrder.SHUFFLE
|
||||
org.jellyfin.playback.core.model.PlaybackOrder.SHUFFLE -> PlaybackOrder.SHUFFLE
|
||||
}
|
||||
runCatching {
|
||||
api.playStateApi.reportPlaybackStart(
|
||||
PlaybackStartInfo(
|
||||
itemId = item.id,
|
||||
playSessionId = stream.identifier,
|
||||
playlistItemId = item.playlistItemId,
|
||||
canSeek = true,
|
||||
isMuted = state.volume.muted,
|
||||
volumeLevel = (state.volume.volume * 100).roundToInt(),
|
||||
isPaused = state.playState.value != PlayState.PLAYING,
|
||||
aspectRatio = state.videoSize.value.aspectRatio.toString(),
|
||||
positionTicks = withContext(Dispatchers.Main) { state.positionInfo.active.inWholeTicks },
|
||||
playMethod = stream.conversionMethod.playMethod,
|
||||
repeatMode = state.repeatMode.value.remoteRepeatMode,
|
||||
nowPlayingQueue = getQueue(),
|
||||
playbackOrder = when (state.playbackOrder.value) {
|
||||
org.jellyfin.playback.core.model.PlaybackOrder.DEFAULT -> PlaybackOrder.DEFAULT
|
||||
org.jellyfin.playback.core.model.PlaybackOrder.RANDOM -> PlaybackOrder.SHUFFLE
|
||||
org.jellyfin.playback.core.model.PlaybackOrder.SHUFFLE -> PlaybackOrder.SHUFFLE
|
||||
}
|
||||
)
|
||||
)
|
||||
)
|
||||
}.onFailure { error -> Timber.w("Failed to send playback start event", error) }
|
||||
}
|
||||
|
||||
private suspend fun sendStreamUpdate() {
|
||||
@@ -98,27 +101,29 @@ class PlaySessionService(
|
||||
val stream = entry.mediaStream ?: return
|
||||
val item = entry.baseItem ?: return
|
||||
|
||||
api.playStateApi.reportPlaybackProgress(
|
||||
PlaybackProgressInfo(
|
||||
itemId = item.id,
|
||||
playSessionId = stream.identifier,
|
||||
playlistItemId = item.playlistItemId,
|
||||
canSeek = true,
|
||||
isMuted = state.volume.muted,
|
||||
volumeLevel = (state.volume.volume * 100).roundToInt(),
|
||||
isPaused = state.playState.value != PlayState.PLAYING,
|
||||
aspectRatio = state.videoSize.value.aspectRatio.toString(),
|
||||
positionTicks = withContext(Dispatchers.Main) { state.positionInfo.active.inWholeTicks },
|
||||
playMethod = stream.conversionMethod.playMethod,
|
||||
repeatMode = state.repeatMode.value.remoteRepeatMode,
|
||||
nowPlayingQueue = getQueue(),
|
||||
playbackOrder = when (state.playbackOrder.value) {
|
||||
org.jellyfin.playback.core.model.PlaybackOrder.DEFAULT -> PlaybackOrder.DEFAULT
|
||||
org.jellyfin.playback.core.model.PlaybackOrder.RANDOM -> PlaybackOrder.SHUFFLE
|
||||
org.jellyfin.playback.core.model.PlaybackOrder.SHUFFLE -> PlaybackOrder.SHUFFLE
|
||||
}
|
||||
runCatching {
|
||||
api.playStateApi.reportPlaybackProgress(
|
||||
PlaybackProgressInfo(
|
||||
itemId = item.id,
|
||||
playSessionId = stream.identifier,
|
||||
playlistItemId = item.playlistItemId,
|
||||
canSeek = true,
|
||||
isMuted = state.volume.muted,
|
||||
volumeLevel = (state.volume.volume * 100).roundToInt(),
|
||||
isPaused = state.playState.value != PlayState.PLAYING,
|
||||
aspectRatio = state.videoSize.value.aspectRatio.toString(),
|
||||
positionTicks = withContext(Dispatchers.Main) { state.positionInfo.active.inWholeTicks },
|
||||
playMethod = stream.conversionMethod.playMethod,
|
||||
repeatMode = state.repeatMode.value.remoteRepeatMode,
|
||||
nowPlayingQueue = getQueue(),
|
||||
playbackOrder = when (state.playbackOrder.value) {
|
||||
org.jellyfin.playback.core.model.PlaybackOrder.DEFAULT -> PlaybackOrder.DEFAULT
|
||||
org.jellyfin.playback.core.model.PlaybackOrder.RANDOM -> PlaybackOrder.SHUFFLE
|
||||
org.jellyfin.playback.core.model.PlaybackOrder.SHUFFLE -> PlaybackOrder.SHUFFLE
|
||||
}
|
||||
)
|
||||
)
|
||||
)
|
||||
}.onFailure { error -> Timber.w("Failed to send playback update event", error) }
|
||||
}
|
||||
|
||||
private suspend fun sendStreamStop() {
|
||||
@@ -126,15 +131,17 @@ class PlaySessionService(
|
||||
val stream = entry.mediaStream ?: return
|
||||
val item = entry.baseItem ?: return
|
||||
|
||||
api.playStateApi.reportPlaybackStopped(
|
||||
PlaybackStopInfo(
|
||||
itemId = item.id,
|
||||
playSessionId = stream.identifier,
|
||||
playlistItemId = item.playlistItemId,
|
||||
positionTicks = withContext(Dispatchers.Main) { state.positionInfo.active.inWholeTicks },
|
||||
failed = false,
|
||||
nowPlayingQueue = getQueue(),
|
||||
runCatching {
|
||||
api.playStateApi.reportPlaybackStopped(
|
||||
PlaybackStopInfo(
|
||||
itemId = item.id,
|
||||
playSessionId = stream.identifier,
|
||||
playlistItemId = item.playlistItemId,
|
||||
positionTicks = withContext(Dispatchers.Main) { state.positionInfo.active.inWholeTicks },
|
||||
failed = false,
|
||||
nowPlayingQueue = getQueue(),
|
||||
)
|
||||
)
|
||||
)
|
||||
}.onFailure { error -> Timber.w("Failed to send playback stop event", error) }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,6 +5,7 @@ import android.content.Context
|
||||
import android.view.ViewGroup
|
||||
import androidx.annotation.OptIn
|
||||
import androidx.core.content.getSystemService
|
||||
import androidx.media3.common.AudioAttributes
|
||||
import androidx.media3.common.C
|
||||
import androidx.media3.common.MediaItem
|
||||
import androidx.media3.common.PlaybackException
|
||||
@@ -101,6 +102,9 @@ class ExoPlayerBackend(
|
||||
setConstantBitrateSeekingAlwaysEnabled(true)
|
||||
}
|
||||
))
|
||||
.setAudioAttributes(AudioAttributes.Builder().apply {
|
||||
setUsage(C.USAGE_MEDIA)
|
||||
}.build(), true)
|
||||
.setPauseAtEndOfMediaItems(true)
|
||||
.build()
|
||||
.also { player ->
|
||||
|
||||
Reference in New Issue
Block a user