Jetpack 可与 MediaRouteActionProvider 组合

问题描述 投票:0回答:2

我目前正在将 Chromcast 集成到我们的应用程序中。我们对所有 UI 元素使用 Jetpack Compose。我们正在使用 TopAppBar 可组合项,我尝试使用 MediaRouteActionProvider 将 Chromecast 按钮添加到其中。我找到使用 MediaRouteActionProvider 的唯一方法是使用 menu.xml 并在 onCreateOptionsMenu 中扩充菜单。

有谁知道在 Jetpack Compose 菜单上下文之外使用 ActionProvider 的方法,还是我现在只能使用菜单?

android android-jetpack-compose chromecast
2个回答
7
投票

更新:决定再次尝试解决这个问题。

import android.content.Context
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.ViewModel
import androidx.lifecycle.ViewModelProvider
import androidx.mediarouter.app.MediaRouteDialogFactory
import androidx.mediarouter.media.MediaControlIntent
import androidx.mediarouter.media.MediaRouteSelector
import com.google.android.gms.cast.framework.CastContext
import com.google.android.gms.cast.framework.CastState
import com.google.android.gms.cast.framework.CastStateListener

class MediaRouteViewModel(context: Context) : ViewModel(), CastStateListener {

    var isCastingAvailable = false
        private set

    val castingStateLiveData = MutableLiveData(CastingState.UNKNOWN)

    val mDialogFactory = MediaRouteDialogFactory.getDefault()

    val mSelector: MediaRouteSelector = MediaRouteSelector.Builder()
        .addControlCategory(MediaControlIntent.CATEGORY_LIVE_AUDIO)
        .addControlCategory(MediaControlIntent.CATEGORY_REMOTE_PLAYBACK)
        .build()

    init {
        try {
            isCastingAvailable = true

            val castingContext = CastContext.getSharedInstance(context)
            castingContext.addCastStateListener(this)
        } catch (e: Exception) {
            // handle me please
        }
    }

    fun shouldShowChooserFragment(): Boolean {
        return when (castingStateLiveData.value) {
            CastingState.NOT_CONNECTED -> true
            CastingState.UNKNOWN -> true
            CastingState.NO_DEVICES_AVAILABLE -> true
            CastingState.CONNECTING -> false
            CastingState.CONNECTED -> false
            else -> false
        }
    }

    enum class CastingState {
        CONNECTED,
        CONNECTING,
        NOT_CONNECTED,
        NO_DEVICES_AVAILABLE,
        UNKNOWN
    }

    class Factory(val context: Context) : ViewModelProvider.Factory {
        override fun <T : ViewModel?> create(modelClass: Class<T>): T {
            return MediaRouteViewModel(context = context) as T
        }
    }

    override fun onCastStateChanged(p0: Int) {
        val castingState = when (p0) {
            CastState.CONNECTED -> CastingState.CONNECTED
            CastState.CONNECTING -> CastingState.CONNECTING
            CastState.NOT_CONNECTED -> CastingState.NOT_CONNECTED
            CastState.NO_DEVICES_AVAILABLE -> CastingState.NO_DEVICES_AVAILABLE
            else -> CastingState.UNKNOWN
        }

        castingStateLiveData.postValue(castingState)
    }
}
@Composable
fun MediaRouter(
    modifier: Modifier,
    iconWidth: Dp,
    fragmentManager: FragmentManager
) {
    val context = LocalContext.current

    val mediaRouteProviderViewModel: MediaRouteViewModel =
        viewModel(factory = MediaRouteViewModel.Factory(context))

    // Can use the casting state to change the painter accordingly
    val castingState = mediaRouteProviderViewModel.castingStateLiveData.observeAsState()

    if (mediaRouteProviderViewModel.isCastingAvailable) {
        Box(modifier = modifier.size(iconWidth)) {
            Image(
                painter = painterResource(id = R.drawable.ic_baseline_cast_connected_24),
                contentDescription = null,
                modifier = Modifier
                    .clickable {
                        val shouldShowChooserFragment = mediaRouteProviderViewModel.shouldShowChooserFragment()
                        val fragmentTag = if (shouldShowChooserFragment) "MediaRouteChooserDialogFragment" else "MediaRouteControllerDialogFragment"
                        
                        val fragment = if (shouldShowChooserFragment) {
                            mediaRouteProviderViewModel.mDialogFactory
                                .onCreateChooserDialogFragment()
                                .apply {
                                    routeSelector = mediaRouteProviderViewModel.mSelector
                                }
                        } else {
                            mediaRouteProviderViewModel.mDialogFactory.onCreateControllerDialogFragment()
                        }

                        val transaction = fragmentManager.beginTransaction()
                        transaction.add(fragment, fragmentTag)
                        transaction.commitAllowingStateLoss()
                    },
                contentScale = ContentScale.FillBounds
            )
        }
    }
}

旧帖子:

我确信在撰写中有更好的方法来做到这一点,但这至少可以完成工作。

class MediaRouteViewModel(context: Context) : ViewModel() {
    private val mediaRouteActionProvider = MediaRouteActionProvider(context)

    val buttonView: View

    init {
        buttonView = mediaRouteActionProvider.onCreateActionView()
        mediaRouteActionProvider.routeSelector = MediaRouteSelector.Builder()
            .addControlCategory(MediaControlIntent.CATEGORY_LIVE_AUDIO)
            .addControlCategory(MediaControlIntent.CATEGORY_REMOTE_PLAYBACK)
            .build()
    }


    fun onClick() {
        mediaRouteActionProvider.onPerformDefaultAction()
    }
}
@Composable
fun MediaRouter(modifier: Modifier, iconWidth: Dp) {
    val context = LocalContext.current

    val mediaRouteProviderViewModel = MediaRouteViewModel(context)

    Box(modifier = modifier.size(iconWidth)) {
        AndroidView(factory = {
            mediaRouteProviderViewModel.buttonView
        }, modifier = Modifier.fillMaxSize())

        Box(modifier = Modifier
            .fillMaxSize()
            .clickable { mediaRouteProviderViewModel.onClick() })
    }
}

0
投票

对于我的情况,我不必使用任何片段管理器。最终得到以下结果。

我有一个数据源类来简单地跟踪应用程序是否连接到投射设备。

interface CastDataSource {
    val castState: StateFlow<Int>
}

class CastDataSourceImpl(private val castContext: CastContext) : CastDataSource {

    private var castSession: CastSession? = null
    private val _castState = MutableStateFlow<Int>(CastState.NOT_CONNECTED)
    override val castState = _castState.asStateFlow()

    init {
        castContext.sessionManager.addSessionManagerListener(object :
            SessionManagerListener<CastSession> {
            override fun onSessionEnded(session: CastSession, error: Int) {
                if (session == castContext) {
                    castSession = null
                }
                _castState.value = CastState.NOT_CONNECTED
            }

            override fun onSessionResumed(session: CastSession, wasSuspended: Boolean) {
                castSession = session
                _castState.value = CastState.CONNECTED
            }

            override fun onSessionStarted(session: CastSession, sessionId: String) {
                castSession = session
                _castState.value = CastState.CONNECTED
            }

            override fun onSessionStarting(session: CastSession) {}
            override fun onSessionStartFailed(session: CastSession, error: Int) {}
            override fun onSessionEnding(session: CastSession) {}
            override fun onSessionResuming(session: CastSession, sessionId: String) {}
            override fun onSessionResumeFailed(session: CastSession, error: Int) {}
            override fun onSessionSuspended(session: CastSession, reason: Int) {}

        }, CastSession::class.java)
    }
}

然后实例化它。 (我在依赖注入中定义了这个)

CastDataSourceImpl(CastContext.getSharedInstance(appContext))

为了简单起见,您可以直接在可组合项中使用此数据源。 (在我的项目中,我更喜欢使用存储库和视图模型。

现在使用此数据源,您可以简单地收集投射状态并根据该状态启动必要的对话框。如果未连接,则使用

chooser
,否则使用
controller

class YourActivity : AppCompatActivity() {
     override fun onCreate(savedInstanceState: Bundle?) {
         ...
         setContent {
             Scaffold(
                    scaffoldState = scaffoldState,
                    topBar = {
                        TopAppBar(
                            title = { Text(text = title) },                             
                            actions = {
                                IconButton(onClick = {
                                    if(castState == CastState.CONNECTED) {
                                        MediaRouteControllerDialog(this@VideoBrowserActivity).show()
                                    } else {
                                        MediaRouteChooserDialog(this@VideoBrowserActivity).apply {
                                            routeSelector = MediaRouteSelector.Builder()
                                                .addControlCategory(MediaControlIntent.CATEGORY_LIVE_AUDIO)
                                                .addControlCategory(MediaControlIntent.CATEGORY_REMOTE_PLAYBACK)
                                                .build()
                                        }.show()
                                    }
                                }) {
                                    Icon(
                                        imageVector = Icons.Filled.Cast,
                                        contentDescription = null
                                    )
                                }
                            }
                        )
                    }
                ) 
         }
     }
}
© www.soinside.com 2019 - 2024. All rights reserved.