Mengintegrasikan Cast ke Aplikasi Android

Panduan developer ini menjelaskan cara menambahkan dukungan Google Cast ke aplikasi pengirim Android menggunakan Android Sender SDK.

Perangkat seluler atau laptop adalah pengirim yang mengontrol pemutaran, dan perangkat Google Cast adalah Penerima yang menampilkan konten di TV.

Framework pengirim mengacu pada biner library class Cast dan resource terkait yang ada saat runtime pada pengirim. Aplikasi pengirim atau aplikasi Cast mengacu pada aplikasi yang juga berjalan di pengirim. Aplikasi Penerima Web mengacu pada aplikasi HTML yang berjalan di perangkat yang kompatibel untuk Cast.

Framework pengirim menggunakan desain callback asinkron untuk memberi tahu aplikasi pengirim peristiwa dan untuk melakukan transisi antara berbagai status siklus proses aplikasi Cast.

Alur aplikasi

Langkah-langkah berikut menjelaskan alur eksekusi tingkat tinggi yang umum untuk aplikasi Android pengirim:

  • Framework Cast akan otomatis memulai penemuan perangkat MediaRouter berdasarkan siklus proses Activity.
  • Saat pengguna mengklik tombol Cast, framework akan menyajikan dialog Transmisi dengan daftar perangkat Cast yang ditemukan.
  • Saat pengguna memilih perangkat Cast, framework akan mencoba meluncurkan aplikasi Penerima Web di perangkat Transmisi.
  • Framework ini memanggil callback di aplikasi pengirim untuk mengonfirmasi bahwa aplikasi Penerima Web telah diluncurkan.
  • Framework ini membuat saluran komunikasi antara aplikasi pengirim dan Penerima Web.
  • Framework ini menggunakan saluran komunikasi untuk memuat dan mengontrol pemutaran media di Penerima Web.
  • Framework menyinkronkan status pemutaran media antara pengirim dan Penerima Web: saat pengguna melakukan tindakan UI pengirim, framework akan meneruskan permintaan kontrol media tersebut ke Penerima Web, dan saat Penerima Web mengirim pembaruan status media, framework akan memperbarui status UI pengirim.
  • Saat pengguna mengklik tombol Cast untuk memutuskan koneksi dari perangkat Cast, framework akan memutuskan koneksi aplikasi pengirim dari Penerima Web.

Untuk daftar lengkap semua class, metode, dan peristiwa di Google Cast Android SDK, lihat Referensi Google Cast Sender API untuk Android. Bagian berikut membahas langkah-langkah untuk menambahkan Cast ke aplikasi Android Anda.

Mengonfigurasi manifes Android

File AndroidManifest.xml di aplikasi Anda mengharuskan Anda mengonfigurasi elemen berikut untuk Cast SDK:

uses-sdk

Tetapkan level API Android minimum dan target yang didukung SDK Cast. Saat ini, batas minimumnya adalah API level 21 dan targetnya adalah API level 28.

<uses-sdk
        android:minSdkVersion="21"
        android:targetSdkVersion="28" />

android:theme

Setel tema aplikasi Anda berdasarkan versi Android SDK minimum. Misalnya, jika tidak mengimplementasikan tema sendiri, Anda harus menggunakan varian Theme.AppCompat saat menargetkan versi minimum Android SDK sebelum Lollipop.

<application
        android:icon="@drawable/ic_launcher"
        android:label="@string/app_name"
        android:theme="@style/Theme.AppCompat" >
       ...
</application>

Melakukan inisialisasi Konteks Cast

Framework ini memiliki objek singleton global, CastContext, yang mengoordinasikan semua interaksi framework.

Aplikasi Anda harus mengimplementasikan antarmuka OptionsProvider untuk menyediakan opsi yang diperlukan guna menginisialisasi singleton CastContext. OptionsProvider menyediakan instance CastOptions yang berisi opsi yang memengaruhi perilaku framework. Yang paling penting adalah ID aplikasi Web Receiver, yang digunakan untuk memfilter hasil penemuan dan meluncurkan aplikasi Web Receiver saat sesi Cast dimulai.

Kotlin
class CastOptionsProvider : OptionsProvider {
    override fun getCastOptions(context: Context): CastOptions {
        return Builder()
            .setReceiverApplicationId(context.getString(R.string.app_id))
            .build()
    }

    override fun getAdditionalSessionProviders(context: Context): List<SessionProvider>? {
        return null
    }
}
Java
public class CastOptionsProvider implements OptionsProvider {
    @Override
    public CastOptions getCastOptions(Context context) {
        CastOptions castOptions = new CastOptions.Builder()
            .setReceiverApplicationId(context.getString(R.string.app_id))
            .build();
        return castOptions;
    }
    @Override
    public List<SessionProvider> getAdditionalSessionProviders(Context context) {
        return null;
    }
}

Anda harus mendeklarasikan nama yang sepenuhnya memenuhi syarat dari OptionsProvider yang diterapkan sebagai kolom metadata dalam file AndroidManifest.xml di aplikasi pengirim:

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

CastContext diinisialisasi dengan lambat saat CastContext.getSharedInstance() dipanggil.

Kotlin
class MyActivity : FragmentActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        val castContext = CastContext.getSharedInstance(this)
    }
}
Java
public class MyActivity extends FragmentActivity {
    @Override
    public void onCreate(Bundle savedInstanceState) {
        CastContext castContext = CastContext.getSharedInstance(this);
    }
}

Widget UX Cast

Framework Cast menyediakan widget yang sesuai dengan Checklist Desain Transmisi:

  • Overlay Pengantar: Framework menyediakan Tampilan kustom, IntroductoryOverlay, yang ditampilkan kepada pengguna untuk menarik perhatian ke tombol Cast saat penerima pertama kali tersedia. Aplikasi Pengirim dapat menyesuaikan teks dan posisi teks judul.

  • Tombol Cast: Tombol Cast terlihat terlepas dari ketersediaan perangkat Cast. Saat pengguna pertama kali mengklik tombol Cast, dialog Cast akan ditampilkan yang mencantumkan perangkat yang ditemukan. Saat pengguna mengklik tombol Cast saat perangkat terhubung, metadata media saat ini akan ditampilkan (seperti judul, nama studio rekaman, dan gambar thumbnail) atau memungkinkan pengguna memutuskan koneksi dari perangkat Transmisi. "Tombol Cast" terkadang disebut sebagai "ikon Cast".

  • Pengontrol Mini: Saat pengguna mentransmisikan konten dan telah keluar dari halaman konten saat ini atau memperluas pengontrol ke layar lain di aplikasi pengirim, pengontrol mini akan ditampilkan di bagian bawah layar untuk memungkinkan pengguna melihat metadata media yang sedang melakukan transmisi dan untuk mengontrol pemutaran.

  • Pengontrol yang Diperluas: Saat pengguna mentransmisikan konten, jika mereka mengklik notifikasi media atau pengontrol mini, pengontrol yang diperluas akan diluncurkan, yang menampilkan metadata media yang sedang diputar dan menyediakan beberapa tombol untuk mengontrol pemutaran media.

  • Notification: Khusus Android. Saat pengguna mentransmisikan konten dan keluar dari aplikasi pengirim, notifikasi media akan ditampilkan yang menunjukkan metadata media dan kontrol pemutaran yang sedang ditransmisikan.

  • Layar Kunci: Khusus Android. Saat pengguna mentransmisikan konten dan membuka (atau waktu waktu perangkat habis) ke layar kunci, kontrol layar kunci media akan ditampilkan yang menampilkan metadata media dan kontrol pemutaran yang sedang ditransmisikan.

Panduan berikut mencakup deskripsi cara menambahkan widget ini ke aplikasi Anda.

Menambahkan Tombol Cast

Android MediaRouter API didesain untuk mengaktifkan tampilan dan pemutaran media di perangkat sekunder. Aplikasi Android yang menggunakan MediaRouter API harus menyertakan tombol Cast sebagai bagian dari antarmuka penggunanya, agar pengguna dapat memilih rute media untuk memutar media di perangkat sekunder seperti perangkat Cast.

Framework ini sangat memudahkan penambahan MediaRouteButton sebagai Cast button. Pertama-tama, Anda harus menambahkan item menu atau MediaRouteButton dalam file xml yang menentukan menu, dan menggunakan CastButtonFactory untuk menghubungkannya dengan framework.

// To add a Cast button, add the following snippet.
// menu.xml
<item
    android:id="@+id/media_route_menu_item"
    android:title="@string/media_route_menu_title"
    app:actionProviderClass="androidx.mediarouter.app.MediaRouteActionProvider"
    app:showAsAction="always" />
Kotlin
// Then override the onCreateOptionMenu() for each of your activities.
// MyActivity.kt
override fun onCreateOptionsMenu(menu: Menu): Boolean {
    super.onCreateOptionsMenu(menu)
    menuInflater.inflate(R.menu.main, menu)
    CastButtonFactory.setUpMediaRouteButton(
        applicationContext,
        menu,
        R.id.media_route_menu_item
    )
    return true
}
Java
// Then override the onCreateOptionMenu() for each of your activities.
// MyActivity.java
@Override public boolean onCreateOptionsMenu(Menu menu) {
    super.onCreateOptionsMenu(menu);
    getMenuInflater().inflate(R.menu.main, menu);
    CastButtonFactory.setUpMediaRouteButton(getApplicationContext(),
                                            menu,
                                            R.id.media_route_menu_item);
    return true;
}

Kemudian, jika Activity mewarisi dari FragmentActivity, Anda dapat menambahkan MediaRouteButton ke tata letak.

// activity_layout.xml
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
   android:layout_width="match_parent"
   android:layout_height="wrap_content"
   android:gravity="center_vertical"
   android:orientation="horizontal" >

   <androidx.mediarouter.app.MediaRouteButton
       android:id="@+id/media_route_button"
       android:layout_width="wrap_content"
       android:layout_height="wrap_content"
       android:layout_weight="1"
       android:mediaRouteTypes="user"
       android:visibility="gone" />

</LinearLayout>
Kotlin
// MyActivity.kt
override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)
    setContentView(R.layout.activity_layout)

    mMediaRouteButton = findViewById<View>(R.id.media_route_button) as MediaRouteButton
    CastButtonFactory.setUpMediaRouteButton(applicationContext, mMediaRouteButton)

    mCastContext = CastContext.getSharedInstance(this)
}
Java
// MyActivity.java
@Override
protected void onCreate(Bundle savedInstanceState) {
   super.onCreate(savedInstanceState);
   setContentView(R.layout.activity_layout);

   mMediaRouteButton = (MediaRouteButton) findViewById(R.id.media_route_button);
   CastButtonFactory.setUpMediaRouteButton(getApplicationContext(), mMediaRouteButton);

   mCastContext = CastContext.getSharedInstance(this);
}

Untuk menyetel tampilan tombol Cast menggunakan tema, lihat Menyesuaikan Tombol Cast.

Mengonfigurasi penemuan perangkat

Penemuan perangkat dikelola sepenuhnya oleh CastContext. Saat menginisialisasi CastContext, aplikasi pengirim menentukan ID aplikasi Web Receiver, dan secara opsional dapat meminta pemfilteran namespace dengan menetapkan supportedNamespaces di CastOptions. CastContext menyimpan referensi ke MediaRouter secara internal, dan akan memulai proses penemuan dalam kondisi berikut:

  • Berdasarkan algoritma yang dirancang untuk menyeimbangkan latensi penemuan perangkat dan penggunaan baterai, penemuan terkadang akan dimulai secara otomatis ketika aplikasi pengirim masuk ke latar depan.
  • Dialog Cast terbuka.
  • SDK Cast mencoba memulihkan sesi Cast.

Proses penemuan akan dihentikan saat dialog Cast ditutup atau aplikasi pengirim memasuki latar belakang.

Kotlin
class CastOptionsProvider : OptionsProvider {
    companion object {
        const val CUSTOM_NAMESPACE = "urn:x-cast:custom_namespace"
    }

    override fun getCastOptions(appContext: Context): CastOptions {
        val supportedNamespaces: MutableList<String> = ArrayList()
        supportedNamespaces.add(CUSTOM_NAMESPACE)

        return CastOptions.Builder()
            .setReceiverApplicationId(context.getString(R.string.app_id))
            .setSupportedNamespaces(supportedNamespaces)
            .build()
    }

    override fun getAdditionalSessionProviders(context: Context): List<SessionProvider>? {
        return null
    }
}
Java
class CastOptionsProvider implements OptionsProvider {
    public static final String CUSTOM_NAMESPACE = "urn:x-cast:custom_namespace";

    @Override
    public CastOptions getCastOptions(Context appContext) {
        List<String> supportedNamespaces = new ArrayList<>();
        supportedNamespaces.add(CUSTOM_NAMESPACE);

        CastOptions castOptions = new CastOptions.Builder()
            .setReceiverApplicationId(context.getString(R.string.app_id))
            .setSupportedNamespaces(supportedNamespaces)
            .build();
        return castOptions;
    }

    @Override
    public List<SessionProvider> getAdditionalSessionProviders(Context context) {
        return null;
    }
}

Cara kerja pengelolaan sesi

SDK Cast memperkenalkan konsep sesi Cast, yang menggabungkan langkah-langkah untuk menghubungkan ke perangkat, meluncurkan (atau bergabung ke) aplikasi Penerima Web, terhubung ke aplikasi tersebut, dan menginisialisasi saluran kontrol media. Lihat Panduan siklus proses aplikasi Penerima Web untuk mengetahui informasi selengkapnya tentang sesi Transmisi dan siklus proses Penerima Web.

Sesi dikelola oleh class SessionManager, yang dapat diakses aplikasi Anda melalui CastContext.getSessionManager(). Sesi individual direpresentasikan oleh subclass class Session. Misalnya, CastSession mewakili sesi dengan perangkat Transmisi. Aplikasi Anda dapat mengakses sesi Cast yang saat ini aktif melalui SessionManager.getCurrentCastSession().

Aplikasi Anda dapat menggunakan class SessionManagerListener untuk memantau peristiwa sesi, seperti pembuatan, penangguhan, dimulainya kembali, dan penghentian. Framework ini secara otomatis mencoba untuk melanjutkan dari penghentian yang tidak normal/tiba-tiba saat sesi aktif.

Sesi dibuat dan dihapus secara otomatis sebagai respons terhadap gestur pengguna dari dialog MediaRouter.

Untuk lebih memahami error memulai Cast, aplikasi dapat menggunakan CastContext#getCastReasonCodeForCastStatusCode(int) untuk mengonversi error awal sesi menjadi CastReasonCodes. Perlu diperhatikan bahwa beberapa error awal sesi (misalnya, CastReasonCodes#CAST_CANCELLED) adalah perilaku yang disengaja dan tidak boleh dicatat dalam log sebagai error.

Jika perlu mengetahui perubahan status untuk sesi, Anda dapat mengimplementasikan SessionManagerListener. Contoh ini memproses ketersediaan CastSession dalam Activity.

Kotlin
class MyActivity : Activity() {
    private var mCastSession: CastSession? = null
    private lateinit var mCastContext: CastContext
    private lateinit var mSessionManager: SessionManager
    private val mSessionManagerListener: SessionManagerListener<CastSession> =
        SessionManagerListenerImpl()

    private inner class SessionManagerListenerImpl : SessionManagerListener<CastSession?> {
        override fun onSessionStarting(session: CastSession?) {}

        override fun onSessionStarted(session: CastSession?, sessionId: String) {
            invalidateOptionsMenu()
        }

        override fun onSessionStartFailed(session: CastSession?, error: Int) {
            val castReasonCode = mCastContext.getCastReasonCodeForCastStatusCode(error)
            // Handle error
        }

        override fun onSessionSuspended(session: CastSession?, reason Int) {}

        override fun onSessionResuming(session: CastSession?, sessionId: String) {}

        override fun onSessionResumed(session: CastSession?, wasSuspended: Boolean) {
            invalidateOptionsMenu()
        }

        override fun onSessionResumeFailed(session: CastSession?, error: Int) {}

        override fun onSessionEnding(session: CastSession?) {}

        override fun onSessionEnded(session: CastSession?, error: Int) {
            finish()
        }
    }

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        mCastContext = CastContext.getSharedInstance(this)
        mSessionManager = mCastContext.sessionManager
    }

    override fun onResume() {
        super.onResume()
        mCastSession = mSessionManager.currentCastSession
        mSessionManager.addSessionManagerListener(mSessionManagerListener, CastSession::class.java)
    }

    override fun onPause() {
        super.onPause()
        mSessionManager.removeSessionManagerListener(mSessionManagerListener, CastSession::class.java)
        mCastSession = null
    }
}
Java
public class MyActivity extends Activity {
    private CastContext mCastContext;
    private CastSession mCastSession;
    private SessionManager mSessionManager;
    private SessionManagerListener<CastSession> mSessionManagerListener =
            new SessionManagerListenerImpl();

    private class SessionManagerListenerImpl implements SessionManagerListener<CastSession> {
        @Override
        public void onSessionStarting(CastSession session) {}
        @Override
        public void onSessionStarted(CastSession session, String sessionId) {
            invalidateOptionsMenu();
        }
        @Override
        public void onSessionStartFailed(CastSession session, int error) {
            int castReasonCode = mCastContext.getCastReasonCodeForCastStatusCode(error);
            // Handle error
        }
        @Override
        public void onSessionSuspended(CastSession session, int reason) {}
        @Override
        public void onSessionResuming(CastSession session, String sessionId) {}
        @Override
        public void onSessionResumed(CastSession session, boolean wasSuspended) {
            invalidateOptionsMenu();
        }
        @Override
        public void onSessionResumeFailed(CastSession session, int error) {}
        @Override
        public void onSessionEnding(CastSession session) {}
        @Override
        public void onSessionEnded(CastSession session, int error) {
            finish();
        }
    }

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        mCastContext = CastContext.getSharedInstance(this);
        mSessionManager = mCastContext.getSessionManager();
    }
    @Override
    protected void onResume() {
        super.onResume();
        mCastSession = mSessionManager.getCurrentCastSession();
        mSessionManager.addSessionManagerListener(mSessionManagerListener, CastSession.class);
    }
    @Override
    protected void onPause() {
        super.onPause();
        mSessionManager.removeSessionManagerListener(mSessionManagerListener, CastSession.class);
        mCastSession = null;
    }
}

Transfer streaming

Mempertahankan status sesi adalah dasar transfer streaming, tempat pengguna dapat memindahkan streaming audio dan video yang ada di seluruh perangkat menggunakan perintah suara, Aplikasi Google Home, atau layar smart. Media berhenti diputar di satu perangkat (sumber) dan berlanjut di perangkat lain (tujuan). Setiap perangkat Cast dengan firmware terbaru dapat berfungsi sebagai sumber atau tujuan dalam transfer streaming.

Untuk mendapatkan perangkat tujuan baru selama transfer streaming atau ekspansi, daftarkan Cast.Listener menggunakan CastSession#addCastListener. Selanjutnya, panggil CastSession#getCastDevice() selama callback onDeviceNameChanged.

Lihat Transfer streaming di Penerima Web untuk mengetahui informasi selengkapnya.

Penyambungan ulang otomatis

Framework ini menyediakan ReconnectionService yang dapat diaktifkan oleh aplikasi pengirim untuk menangani koneksi ulang dalam banyak kasus sudut yang tidak jelas, seperti:

  • Memulihkan Wi-Fi untuk sementara waktu
  • Pulihkan dari mode tidur perangkat
  • Memulihkan dari latar belakang aplikasi
  • Memulihkan jika aplikasi mengalami error

Layanan ini diaktifkan secara default, dan dapat dinonaktifkan di CastOptions.Builder.

Layanan ini dapat otomatis digabungkan ke dalam manifes aplikasi Anda jika penggabungan otomatis diaktifkan di file gradle Anda.

Framework akan memulai layanan saat ada sesi media, dan menghentikannya saat sesi media berakhir.

Cara kerja Kontrol Media

Framework Cast menghentikan penggunaan class RemoteMediaPlayer dari Cast 2.x untuk mendukung class RemoteMediaClient baru, yang menyediakan fungsi yang sama dalam sekumpulan API yang lebih praktis, dan tidak perlu meneruskan GoogleApiClient.

Saat aplikasi Anda membuat CastSession dengan aplikasi Web Receiver yang mendukung namespace media, instance RemoteMediaClient akan otomatis dibuat oleh framework; aplikasi Anda dapat mengaksesnya dengan memanggil metode getRemoteMediaClient() pada instance CastSession.

Semua metode RemoteMediaClient yang mengeluarkan permintaan ke Penerima Web akan menampilkan objek PendingResult yang dapat digunakan untuk melacak permintaan tersebut.

Instance RemoteMediaClient diharapkan dapat dibagikan oleh beberapa bagian aplikasi Anda, dan memang beberapa komponen internal framework, seperti pengontrol mini persisten dan layanan notifikasi. Untuk itu, instance ini mendukung pendaftaran beberapa instance RemoteMediaClient.Listener.

Menetapkan metadata media

Class MediaMetadata mewakili informasi tentang item media yang ingin Anda Transmisi. Contoh berikut membuat instance MediaMetadata baru dari sebuah film dan menyetel judul, subtitel, dan dua gambar.

Kotlin
val movieMetadata = MediaMetadata(MediaMetadata.MEDIA_TYPE_MOVIE)

movieMetadata.putString(MediaMetadata.KEY_TITLE, mSelectedMedia.getTitle())
movieMetadata.putString(MediaMetadata.KEY_SUBTITLE, mSelectedMedia.getStudio())
movieMetadata.addImage(WebImage(Uri.parse(mSelectedMedia.getImage(0))))
movieMetadata.addImage(WebImage(Uri.parse(mSelectedMedia.getImage(1))))
Java
MediaMetadata movieMetadata = new MediaMetadata(MediaMetadata.MEDIA_TYPE_MOVIE);

movieMetadata.putString(MediaMetadata.KEY_TITLE, mSelectedMedia.getTitle());
movieMetadata.putString(MediaMetadata.KEY_SUBTITLE, mSelectedMedia.getStudio());
movieMetadata.addImage(new WebImage(Uri.parse(mSelectedMedia.getImage(0))));
movieMetadata.addImage(new WebImage(Uri.parse(mSelectedMedia.getImage(1))));

Baca Pemilihan Gambar tentang penggunaan gambar dengan metadata media.

Memuat media

Aplikasi Anda dapat memuat item media, seperti yang ditunjukkan pada kode berikut. Pertama-tama, gunakan MediaInfo.Builder dengan metadata media untuk membangun instance MediaInfo. Dapatkan RemoteMediaClient dari CastSession saat ini, lalu muat MediaInfo ke RemoteMediaClient tersebut. Gunakan RemoteMediaClient untuk memutar, menjeda, dan mengontrol aplikasi pemutar media yang berjalan di Penerima Web.

Kotlin
val mediaInfo = MediaInfo.Builder(mSelectedMedia.getUrl())
    .setStreamType(MediaInfo.STREAM_TYPE_BUFFERED)
    .setContentType("videos/mp4")
    .setMetadata(movieMetadata)
    .setStreamDuration(mSelectedMedia.getDuration() * 1000)
    .build()
val remoteMediaClient = mCastSession.getRemoteMediaClient()
remoteMediaClient.load(MediaLoadRequestData.Builder().setMediaInfo(mediaInfo).build())
Java
MediaInfo mediaInfo = new MediaInfo.Builder(mSelectedMedia.getUrl())
        .setStreamType(MediaInfo.STREAM_TYPE_BUFFERED)
        .setContentType("videos/mp4")
        .setMetadata(movieMetadata)
        .setStreamDuration(mSelectedMedia.getDuration() * 1000)
        .build();
RemoteMediaClient remoteMediaClient = mCastSession.getRemoteMediaClient();
remoteMediaClient.load(new MediaLoadRequestData.Builder().setMediaInfo(mediaInfo).build());

Lihat juga bagian tentang menggunakan trek media.

Format video 4K

Untuk memeriksa format video media Anda, gunakan getVideoInfo() di MediaStatus untuk mendapatkan instance VideoInfo saat ini. Instance ini berisi jenis format HDR TV serta tinggi dan lebar tampilan dalam piksel. Varian format 4K ditunjukkan oleh konstanta HDR_TYPE_*.

Notifikasi remote control ke beberapa perangkat

Saat pengguna melakukan transmisi, perangkat Android lain di jaringan yang sama akan mendapatkan notifikasi yang memungkinkan mereka mengontrol pemutaran. Siapa pun yang perangkatnya menerima notifikasi tersebut dapat menonaktifkannya untuk perangkat tersebut melalui aplikasi Setelan di Google > Google Cast > Tampilkan notifikasi remote control. (Notifikasi tersebut menyertakan pintasan ke aplikasi Setelan.) Untuk detail selengkapnya, lihat Notifikasi remote control Cast.

Menambahkan pengontrol mini

Menurut Checklist Desain Cast, aplikasi pengirim harus memberikan kontrol persisten yang dikenal sebagai pengontrol mini yang akan muncul saat pengguna keluar dari halaman konten saat ini ke bagian lain dari aplikasi pengirim. Pengontrol mini memberikan pengingat yang terlihat kepada pengguna tentang sesi Transmisi saat ini. Dengan mengetuk pengontrol mini, pengguna dapat kembali ke tampilan pengontrol layar penuh Cast yang diperluas.

Framework ini menyediakan Tampilan kustom, MiniControllerFragment, yang dapat Anda tambahkan ke bagian bawah file tata letak dari setiap aktivitas tempat Anda ingin menampilkan pengontrol mini.

<fragment
    android:id="@+id/castMiniController"
    android:layout_width="fill_parent"
    android:layout_height="wrap_content"
    android:layout_alignParentBottom="true"
    android:visibility="gone"
    class="com.google.android.gms.cast.framework.media.widget.MiniControllerFragment" />

Saat aplikasi pengirim memutar live stream video atau audio, SDK akan otomatis menampilkan tombol putar/berhenti sebagai pengganti tombol putar/jeda di pengontrol mini.

Untuk menyetel tampilan teks judul dan subjudul tampilan kustom ini, serta untuk memilih tombol, lihat Menyesuaikan Pengontrol Mini.

Tambahkan pengontrol yang diperluas

Checklist Desain Google Cast mengharuskan aplikasi pengirim menyediakan pengontrol yang diperluas untuk media yang sedang Ditransmisikan. Pengontrol yang diperluas adalah versi layar penuh dari pengontrol mini.

SDK Cast menyediakan widget untuk pengontrol yang diperluas yang disebut ExpandedControllerActivity. Ini adalah class abstrak yang harus dibuat subclass untuk menambahkan tombol Cast.

Pertama, buat file resource menu baru untuk pengontrol yang diperluas guna menyediakan tombol Cast:

<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="@string/media_route_menu_title"
            app:actionProviderClass="androidx.mediarouter.app.MediaRouteActionProvider"
            app:showAsAction="always"/>

</menu>

Buat class baru yang memperluas ExpandedControllerActivity.

Kotlin
class ExpandedControlsActivity : ExpandedControllerActivity() {
    override fun onCreateOptionsMenu(menu: Menu): Boolean {
        super.onCreateOptionsMenu(menu)
        menuInflater.inflate(R.menu.expanded_controller, menu)
        CastButtonFactory.setUpMediaRouteButton(this, menu, R.id.media_route_menu_item)
        return true
    }
}
Java
public class ExpandedControlsActivity extends ExpandedControllerActivity {
    @Override
    public boolean onCreateOptionsMenu(Menu menu) {
        super.onCreateOptionsMenu(menu);
        getMenuInflater().inflate(R.menu.expanded_controller, menu);
        CastButtonFactory.setUpMediaRouteButton(this, menu, R.id.media_route_menu_item);
        return true;
    }
}

Sekarang deklarasikan aktivitas baru Anda di manifes aplikasi dalam tag application:

<application>
...
<activity
        android:name=".expandedcontrols.ExpandedControlsActivity"
        android:label="@string/app_name"
        android:launchMode="singleTask"
        android:theme="@style/Theme.CastVideosDark"
        android:screenOrientation="portrait"
        android:parentActivityName="com.google.sample.cast.refplayer.VideoBrowserActivity">
    <intent-filter>
        <action android:name="android.intent.action.MAIN"/>
    </intent-filter>
</activity>
...
</application>

Edit CastOptionsProvider lalu ubah NotificationOptions dan CastMediaOptions untuk menetapkan aktivitas target ke aktivitas baru Anda:

Kotlin
override fun getCastOptions(context: Context): CastOptions? {
    val notificationOptions = NotificationOptions.Builder()
        .setTargetActivityClassName(ExpandedControlsActivity::class.java.name)
        .build()
    val mediaOptions = CastMediaOptions.Builder()
        .setNotificationOptions(notificationOptions)
        .setExpandedControllerActivityClassName(ExpandedControlsActivity::class.java.name)
        .build()

    return CastOptions.Builder()
        .setReceiverApplicationId(context.getString(R.string.app_id))
        .setCastMediaOptions(mediaOptions)
        .build()
}
Java
public CastOptions getCastOptions(Context context) {
    NotificationOptions notificationOptions = new NotificationOptions.Builder()
            .setTargetActivityClassName(ExpandedControlsActivity.class.getName())
            .build();
    CastMediaOptions mediaOptions = new CastMediaOptions.Builder()
            .setNotificationOptions(notificationOptions)
            .setExpandedControllerActivityClassName(ExpandedControlsActivity.class.getName())
            .build();

    return new CastOptions.Builder()
            .setReceiverApplicationId(context.getString(R.string.app_id))
            .setCastMediaOptions(mediaOptions)
            .build();
}

Perbarui metode LocalPlayerActivity loadRemoteMedia untuk menampilkan aktivitas baru Anda saat media jarak jauh dimuat:

Kotlin
private fun loadRemoteMedia(position: Int, autoPlay: Boolean) {
    val remoteMediaClient = mCastSession?.remoteMediaClient ?: return

    remoteMediaClient.registerCallback(object : RemoteMediaClient.Callback() {
        override fun onStatusUpdated() {
            val intent = Intent(this@LocalPlayerActivity, ExpandedControlsActivity::class.java)
            startActivity(intent)
            remoteMediaClient.unregisterCallback(this)
        }
    })

    remoteMediaClient.load(
        MediaLoadRequestData.Builder()
            .setMediaInfo(mSelectedMedia)
            .setAutoplay(autoPlay)
            .setCurrentTime(position.toLong()).build()
    )
}
Java
private void loadRemoteMedia(int position, boolean autoPlay) {
    if (mCastSession == null) {
        return;
    }
    final RemoteMediaClient remoteMediaClient = mCastSession.getRemoteMediaClient();
    if (remoteMediaClient == null) {
        return;
    }
    remoteMediaClient.registerCallback(new RemoteMediaClient.Callback() {
        @Override
        public void onStatusUpdated() {
            Intent intent = new Intent(LocalPlayerActivity.this, ExpandedControlsActivity.class);
            startActivity(intent);
            remoteMediaClient.unregisterCallback(this);
        }
    });
    remoteMediaClient.load(new MediaLoadRequestData.Builder()
            .setMediaInfo(mSelectedMedia)
            .setAutoplay(autoPlay)
            .setCurrentTime(position).build());
}

Saat aplikasi pengirim memutar live stream video atau audio, SDK akan otomatis menampilkan tombol putar/berhenti sebagai pengganti tombol putar/jeda di pengontrol yang diperluas.

Untuk menyetel tampilan menggunakan tema, pilih tombol yang akan ditampilkan, dan tambahkan tombol kustom, lihat Menyesuaikan Pengontrol yang Diperluas.

Kontrol volume

Framework secara otomatis mengelola volume untuk aplikasi pengirim. Framework ini akan otomatis menyinkronkan aplikasi pengirim dan Penerima Web sehingga UI pengirim selalu melaporkan volume yang ditentukan oleh Penerima Web.

Kontrol volume tombol fisik

Di Android, tombol fisik di perangkat pengirim dapat digunakan untuk mengubah volume sesi Transmisi di Penerima Web secara default untuk perangkat apa pun yang menggunakan Jelly Bean atau yang lebih baru.

Kontrol volume tombol fisik sebelum Jelly Bean

Untuk menggunakan tombol volume fisik guna mengontrol volume perangkat Penerima Web di perangkat Android yang lebih lama dari Jelly Bean, aplikasi pengirim harus mengganti dispatchKeyEvent dalam Aktivitasnya, dan memanggil CastContext.onDispatchVolumeKeyEventBeforeJellyBean():

Kotlin
class MyActivity : FragmentActivity() {
    override fun dispatchKeyEvent(event: KeyEvent): Boolean {
        return (CastContext.getSharedInstance(this)
            .onDispatchVolumeKeyEventBeforeJellyBean(event)
                || super.dispatchKeyEvent(event))
    }
}
Java
class MyActivity extends FragmentActivity {
    @Override
    public boolean dispatchKeyEvent(KeyEvent event) {
        return CastContext.getSharedInstance(this)
            .onDispatchVolumeKeyEventBeforeJellyBean(event)
            || super.dispatchKeyEvent(event);
    }
}

Tambahkan kontrol media ke notifikasi dan layar kunci

Khusus Android, Checklist Desain Google Cast memerlukan aplikasi pengirim untuk mengimplementasikan kontrol media dalam notifikasi dan di layar kunci, tempat pengirim melakukan transmisi, tetapi aplikasi pengirim tidak memiliki fokus. Framework ini menyediakan MediaNotificationService dan MediaIntentReceiver untuk membantu aplikasi pengirim membuat kontrol media dalam notifikasi dan di layar kunci.

MediaNotificationService berjalan saat pengirim melakukan transmisi, dan akan menampilkan notifikasi dengan thumbnail gambar dan informasi tentang item transmisi saat ini, tombol putar/jeda, dan tombol berhenti.

MediaIntentReceiver adalah BroadcastReceiver yang menangani tindakan pengguna dari notifikasi.

Aplikasi dapat mengonfigurasi notifikasi dan kontrol media dari layar kunci melalui NotificationOptions. Aplikasi Anda dapat mengonfigurasi tombol kontrol yang akan ditampilkan dalam notifikasi, dan Activity mana yang akan dibuka saat notifikasi diketuk oleh pengguna. Jika tindakan tidak disediakan secara eksplisit, nilai default, MediaIntentReceiver.ACTION_TOGGLE_PLAYBACK dan MediaIntentReceiver.ACTION_STOP_CASTING akan digunakan.

Kotlin
// Example showing 4 buttons: "rewind", "play/pause", "forward" and "stop casting".
val buttonActions: MutableList<String> = ArrayList()
buttonActions.add(MediaIntentReceiver.ACTION_REWIND)
buttonActions.add(MediaIntentReceiver.ACTION_TOGGLE_PLAYBACK)
buttonActions.add(MediaIntentReceiver.ACTION_FORWARD)
buttonActions.add(MediaIntentReceiver.ACTION_STOP_CASTING)

// Showing "play/pause" and "stop casting" in the compat view of the notification.
val compatButtonActionsIndices = intArrayOf(1, 3)

// Builds a notification with the above actions. Each tap on the "rewind" and "forward" buttons skips 30 seconds.
// Tapping on the notification opens an Activity with class VideoBrowserActivity.
val notificationOptions = NotificationOptions.Builder()
    .setActions(buttonActions, compatButtonActionsIndices)
    .setSkipStepMs(30 * DateUtils.SECOND_IN_MILLIS)
    .setTargetActivityClassName(VideoBrowserActivity::class.java.name)
    .build()
Java
// Example showing 4 buttons: "rewind", "play/pause", "forward" and "stop casting".
List<String> buttonActions = new ArrayList<>();
buttonActions.add(MediaIntentReceiver.ACTION_REWIND);
buttonActions.add(MediaIntentReceiver.ACTION_TOGGLE_PLAYBACK);
buttonActions.add(MediaIntentReceiver.ACTION_FORWARD);
buttonActions.add(MediaIntentReceiver.ACTION_STOP_CASTING);

// Showing "play/pause" and "stop casting" in the compat view of the notification.
int[] compatButtonActionsIndices = new int[]{1, 3};

// Builds a notification with the above actions. Each tap on the "rewind" and "forward" buttons skips 30 seconds.
// Tapping on the notification opens an Activity with class VideoBrowserActivity.
NotificationOptions notificationOptions = new NotificationOptions.Builder()
    .setActions(buttonActions, compatButtonActionsIndices)
    .setSkipStepMs(30 * DateUtils.SECOND_IN_MILLIS)
    .setTargetActivityClassName(VideoBrowserActivity.class.getName())
    .build();

Menampilkan kontrol media dari notifikasi dan layar kunci diaktifkan secara default, dan dapat dinonaktifkan dengan memanggil setNotificationOptions dengan null di CastMediaOptions.Builder. Saat ini, fitur layar kunci diaktifkan selama notifikasi diaktifkan.

Kotlin
// ... continue with the NotificationOptions built above
val mediaOptions = CastMediaOptions.Builder()
    .setNotificationOptions(notificationOptions)
    .build()
val castOptions: CastOptions = Builder()
    .setReceiverApplicationId(context.getString(R.string.app_id))
    .setCastMediaOptions(mediaOptions)
    .build()
Java
// ... continue with the NotificationOptions built above
CastMediaOptions mediaOptions = new CastMediaOptions.Builder()
        .setNotificationOptions(notificationOptions)
        .build();
CastOptions castOptions = new CastOptions.Builder()
        .setReceiverApplicationId(context.getString(R.string.app_id))
        .setCastMediaOptions(mediaOptions)
        .build();

Saat aplikasi pengirim memutar live stream video atau audio, SDK akan otomatis menampilkan tombol putar/berhenti sebagai pengganti tombol putar/jeda pada kontrol notifikasi, tetapi tidak dengan kontrol layar kunci.

Catatan: Untuk menampilkan kontrol layar kunci di perangkat sebelum versi Lollipop, RemoteMediaClient akan otomatis meminta fokus audio untuk Anda.

Menangani error

Aplikasi pengirim harus menangani semua callback error dan menentukan respons terbaik untuk setiap tahap siklus proses Transmisi. Aplikasi dapat menampilkan dialog error kepada pengguna atau dapat memutuskan untuk memutus koneksi ke Penerima Web.