반응형

Chromecast

설명

  • 애플리케이션에서 동영상을 Chromecast 디바이스에 연결하여 TV나 모니터로 송출하는 방법
  • 과정중에 receiverApplicationId 설정이 필요한데 이는 Google Cast SDK Developer Console에 등록해서 발급받아야 한다.(유료인듯..?)

예제

build.gradle
dependencies {
    ...
    implementation 'com.google.android.gms:play-services-cast-framework:17.0.0'
}
res/menu/main.xml
<?xml version="1.0" encoding="utf-8"?>
<menu xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto">
    <item
        android:id="@+id/media_route_menu_item"
        android:title="TEST"
        app:actionProviderClass="androidx.mediarouter.app.MediaRouteActionProvider"
        app:showAsAction="always" />
</menu>
res/layout/activity_main.xml
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical">

    <Button
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:onClick="onClickStart"
        android:text="start" />

    <Button
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:onClick="onClickStop"
        android:text="stop" />

    <ScrollView
        android:layout_width="match_parent"
        android:layout_height="wrap_content">

        <TextView
            android:id="@+id/textView"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:padding="10dp"
            android:text="" />
    </ScrollView>
</LinearLayout>
MainActivity
class MainActivity : AppCompatActivity() {
    private val castStateListener = CastStateListenerImpl()
    private val sessionManagerListener = SessionManagerListenerImpl()

    private lateinit var textView: TextView
    private lateinit var castContext: CastContext
    private lateinit var mediaRouteMenuItem: MenuItem

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)

        textView = findViewById(R.id.textView)
        castContext = CastContext.getSharedInstance(this)
    }

    override fun onCreateOptionsMenu(menu: Menu?): Boolean {
        super.onCreateOptionsMenu(menu)
        menuInflater.inflate(R.menu.main, menu)
        mediaRouteMenuItem = CastButtonFactory.setUpMediaRouteButton(applicationContext, menu, R.id.media_route_menu_item)
        return true
    }

    override fun dispatchKeyEvent(event: KeyEvent?): Boolean {
        return castContext.onDispatchVolumeKeyEventBeforeJellyBean(event) || super.dispatchKeyEvent(event)
    }

    override fun onResume() {
        super.onResume()
        castContext.addCastStateListener(castStateListener)
        castContext.sessionManager.addSessionManagerListener(sessionManagerListener, CastSession::class.java)
    }

    override fun onPause() {
        super.onPause()
        castContext.removeCastStateListener(castStateListener)
        castContext.sessionManager.removeSessionManagerListener(sessionManagerListener, CastSession::class.java)
    }

    fun onClickStart(view: View) {
        val castSession = castContext.sessionManager.currentCastSession

        if (castSession == null || !castSession.isConnected) {
            showInfo("should enable chromecast")
            return
        }

        val movieMetadata = MediaMetadata(MediaMetadata.MEDIA_TYPE_MOVIE)
        movieMetadata.putString(MediaMetadata.KEY_SUBTITLE, "Google IO - 2014")
        movieMetadata.putString(MediaMetadata.KEY_TITLE, "Designing For Google Cast")
        movieMetadata.addImage(WebImage(Uri.parse("https://commondatastorage.googleapis.com/gtv-videos-bucket/CastVideos/images/480x270/DesigningForGoogleCast2-480x270.jpg")))
        movieMetadata.addImage(WebImage(Uri.parse("https://commondatastorage.googleapis.com/gtv-videos-bucket/CastVideos/images/780x1200/DesigningForGoogleCast-887x1200.jpg")))

        val mediaTrack = MediaTrack.Builder(1, 1)
            .setName("English Subtitle")
            .setSubtype(MediaTrack.SUBTYPE_CAPTIONS)
            .setContentId("https://commondatastorage.googleapis.com/gtv-videos-bucket/CastVideos/tracks/DesigningForGoogleCast-en.vtt")
            .setLanguage("en-US")
            .build()

        val mediaInfo = MediaInfo.Builder("https://commondatastorage.googleapis.com/gtv-videos-bucket/CastVideos/hls/DesigningForGoogleCast.m3u8")
            .setStreamType(MediaInfo.STREAM_TYPE_BUFFERED)
            .setContentType("application/x-mpegurl")
            .setMetadata(movieMetadata)
            .setMediaTracks(listOf(mediaTrack))
            .setStreamDuration(333 * 1000)
            // .setCustomData(jsonObj)
            .build()

        val remoteMediaClient = castSession.remoteMediaClient
        remoteMediaClient.stop()

        /*remoteMediaClient.registerCallback(object : RemoteMediaClient.Callback() {
            override fun onStatusUpdated() {
                startActivity(Intent(this@PlayerActivity, ExpandedControllerActivity::class.java))
                remoteMediaClient.unregisterCallback(this)
            }
        })*/

        remoteMediaClient.load(MediaLoadRequestData.Builder()
            .setMediaInfo(mediaInfo)
            // .setAutoplay(true)
            .setCurrentTime(0)
            .build())

        remoteMediaClient.play()
    }

    fun onClickStop(view: View) {
        val castSession = castContext.sessionManager.currentCastSession

        if (castSession == null || !castSession.isConnected) {
            showInfo("should enable chromecast")
            return
        }

        val remoteMediaClient = castSession.remoteMediaClient
        remoteMediaClient.stop()
    }

    private fun showInfo(message: String) {
        textView.append("${message}\n\n")
    }

    private inner class CastStateListenerImpl : CastStateListener {
        override fun onCastStateChanged(state: Int) {
            if (state != CastState.NO_DEVICES_AVAILABLE) {
                showInfo("[onCastStateChanged] state : ${state}")
            }
        }
    }

    private inner class SessionManagerListenerImpl : SessionManagerListener<CastSession> {
        override fun onSessionStarting(session: CastSession?) {
            showInfo("[onSessionStarting] session : ${session}")
        }

        override fun onSessionStarted(session: CastSession?, sessionId: String?) {
            showInfo("[onSessionStarted] session : ${session}, sessionId : ${sessionId}")
        }

        override fun onSessionStartFailed(session: CastSession?, error: Int) {
            showInfo("[onSessionStartFailed] session : ${session}, error : ${error}")
        }

        override fun onSessionEnding(session: CastSession?) {
            showInfo("[onSessionEnding] session : ${session}")
        }

        override fun onSessionEnded(session: CastSession?, error: Int) {
            showInfo("[onSessionEnded] session : ${session}, error : ${error}")
        }

        override fun onSessionResuming(session: CastSession?, sessionId: String?) {
            showInfo("[onSessionResuming] session : ${session}, sessionId : ${sessionId}")
        }

        override fun onSessionResumed(session: CastSession?, wasSuspended: Boolean) {
            showInfo("[onSessionResumed] session : ${session}, wasSuspended : ${wasSuspended}")
        }

        override fun onSessionResumeFailed(session: CastSession?, error: Int) {
            showInfo("[onSessionResumeFailed] session : ${session}, error : ${error}")
        }

        override fun onSessionSuspended(session: CastSession?, reason: Int) {
            showInfo("[onSessionSuspended] session : ${session}, reason : ${reason}")
        }
    }
}
CastOptionsProvider
class CastOptionsProvider : OptionsProvider {
    override fun getCastOptions(context: Context): CastOptions {
        return CastOptions.Builder()
            .setReceiverApplicationId("C0868879") // 테스트용 applicationId
            .build()
    }

    override fun getAdditionalSessionProviders(context: Context): MutableList<SessionProvider>? {
        return null
    }
}
AndroidManifest.xml
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.clone.android_chromecast">

    <uses-permission android:name="android.permission.INTERNET" />

    <application
        android:allowBackup="true"
        android:icon="@mipmap/ic_launcher"
        android:label="@string/app_name"
        android:roundIcon="@mipmap/ic_launcher_round"
        android:supportsRtl="true"
        android:theme="@style/Theme.Androidchromecast">

        <meta-data
            android:name="com.google.android.gms.cast.framework.OPTIONS_PROVIDER_CLASS_NAME"
            android:value="com.clone.android_chromecast.CastOptionsProvider" />

        <activity android:name=".MainActivity">
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />
                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>

        <activity
            android:name="com.google.android.gms.cast.framework.media.widget.ExpandedControllerActivity"
            android:theme="@style/Theme.AppCompat.NoActionBar" />
    </application>
</manifest>

참고

반응형

'Development > Android' 카테고리의 다른 글

[Android] NDK  (0) 2021.09.25
[Android] Jenkins로 APK 빌드하기  (0) 2021.02.22
[Android] ExoPlayer  (0) 2021.02.10
[Android] ViewTreeObserver  (0) 2021.02.10
[Android] LifecycleObserver  (0) 2021.02.10

+ Recent posts