1. סקירה כללית

ב-Codelab הזה תלמדו איך לשנות אפליקציית וידאו קיימת ל-Android כדי להפעיל Cast של תוכן במכשיר שתומך ב-Google Cast.
מה זה Google Cast?
עם Google Cast, משתמשים יכולים להפעיל Cast של תוכן מנייד לטלוויזיה. לאחר מכן, המשתמשים יכולים להשתמש בנייד כשלט רחוק להפעלת מדיה בטלוויזיה.
ה-SDK של Google Cast מאפשר להרחיב את האפליקציה כדי לשלוט בטלוויזיה או במערכת שמע. Cast SDK מאפשר לכם להוסיף את רכיבי ממשק המשתמש הנדרשים על סמך רשימת המשימות לעיצוב של Google Cast.
הכנו רשימת משימות לעיצוב Google Cast כדי שחוויית המשתמש תהיה פשוטה וצפויה בכל הפלטפורמות הנתמכות.
מה אנחנו הולכים לבנות?
בסיום ה-codelab הזה, תהיה לכם אפליקציית וידאו ל-Android שתוכל לשדר סרטונים למכשיר שתומך ב-Google Cast.
מה תלמדו
- איך מוסיפים את Google Cast SDK לאפליקציית וידאו לדוגמה.
- איך מוסיפים את הכפתור להפעלת Cast כדי לבחור מכשיר Cast.
- איך מתחברים למכשיר Cast ומפעילים מקלט מדיה
- איך מפעילים Cast של סרטון.
- איך מוסיפים לאפליקציה את המיני-בקר של Cast.
- איך תומכים בהתראות על מדיה ובאמצעי בקרה במסך הנעילה.
- איך מוסיפים בקר מורחב
- איך מספקים שכבת-על של מבצע היכרות.
- איך מתאימים אישית את הווידג'טים של Cast.
- איך משלבים את Cast Connect
הדרישות
- Android SDK העדכני.
- Android Studio מגרסה 3.2 ואילך
- מכשיר נייד אחד עם Android 4.1+ Jelly Bean (רמת API 16).
- כבל USB להעברת נתונים כדי לחבר את המכשיר הנייד למחשב הפיתוח.
- מכשיר Cast, כמו Chromecast או Android TV, שמחובר לאינטרנט.
- טלוויזיה או צג עם כניסת HDMI.
- כדי לבדוק את השילוב של Cast Connect, צריך Chromecast with Google TV, אבל הוא לא נדרש לשאר חלקי ה-Codelab. אם אין לכם כזה, אתם יכולים לדלג על השלב הוספת תמיכה ב-Cast Connect, לקראת סוף המדריך הזה.
חוויה
- נדרש ידע קודם בפיתוח ב-Kotlin וב-Android.
- תצטרכו גם ידע קודם בצפייה בטלוויזיה :)
איך תשתמשו במדריך הזה?
איזה דירוג מגיע לדעתך לחוויית הפיתוח של אפליקציות ל-Android?
איך היית מדרג את חוויית הצפייה בטלוויזיה?
2. קבלת קוד לדוגמה
אפשר להוריד את כל קוד הדוגמה למחשב...
ומחלצים את קובץ ה-ZIP שהורד.
3. הרצת האפליקציה לדוגמה

קודם נראה איך נראית אפליקציית הדוגמה המלאה. האפליקציה היא נגן וידאו בסיסי. המשתמש יכול לבחור סרטון מתוך רשימה ואז להפעיל אותו באופן מקומי במכשיר או להפעיל אותו באמצעות Cast במכשיר Google Cast.
אחרי שמורידים את הקוד, פועלים לפי ההוראות הבאות כדי לפתוח ולהריץ את האפליקציה לדוגמה ב-Android Studio:
בוחרים באפשרות Import Project (ייבוא פרויקט) במסך הפתיחה או באפשרויות התפריט File > New > Import Project... (קובץ > חדש > ייבוא פרויקט…).
בוחרים את הספרייה 
app-done מתיקיית קוד הדוגמה ולוחצים על OK (אישור).
לוחצים על File >
Sync Project with Gradle Files (קובץ > סנכרון הפרויקט עם קובצי Gradle).
מפעילים את ניפוי הבאגים ב-USB במכשיר Android – ב-Android מגרסה 4.2 ואילך, מסך האפשרויות למפתחים מוסתר כברירת מחדל. כדי להציג את האפשרות הזו, עוברים אל הגדרות > מידע על הטלפון ומקישים על מספר Build שבע פעמים. חוזרים למסך הקודם, עוברים אל מערכת > הגדרות מתקדמות, מקישים על אפשרויות למפתחים קרוב לתחתית, ואז מקישים על ניפוי באגים ב-USB כדי להפעיל אותו.
מחברים את מכשיר Android ולוחצים על הלחצן
Run (הפעלה) ב-Android Studio. אחרי כמה שניות, אפליקציית הווידאו בשם Cast Videos תופיע.
לוחצים על הכפתור להפעלת Cast באפליקציית הסרטונים ובוחרים את מכשיר Cast.
בוחרים סרטון ולוחצים על לחצן ההפעלה.
הסרטון יתחיל לפעול במכשיר Cast.
השלט המורחב יוצג. אפשר להשתמש בלחצן ההפעלה או ההשהיה כדי לשלוט בהפעלה.
חוזרים לרשימת הסרטונים.
בשלב הזה יופיע בקר קטן בתחתית המסך. 
לוחצים על לחצן ההשהיה במיני-בקר כדי להשהות את הסרטון במכשיר המקלט. כדי להמשיך להפעיל את הסרטון, לוחצים על לחצן ההפעלה במיני-בקר.
לוחצים על הכפתור הראשי במכשיר הנייד. מושכים למטה את ההתראות, ועכשיו אמורה להופיע התראה לגבי סשן ה-Cast.
נועלים את הטלפון, וכשפותחים את הנעילה אמורה להופיע התראה במסך הנעילה לשליטה בהפעלת המדיה או להפסקת ה-Cast.
חוזרים לאפליקציית הווידאו ולוחצים על הכפתור להפעלת Cast כדי להפסיק את ההעברה במכשיר Cast.
שאלות נפוצות
4. הכנת פרויקט ההתחלה

אנחנו צריכים להוסיף תמיכה ב-Google Cast לאפליקציית ההפעלה שהורדת. הנה כמה מונחים שקשורים ל-Google Cast שבהם נשתמש ב-codelab הזה:
- אפליקציית השולח פועלת במכשיר נייד או במחשב נייד,
- אפליקציית מקלט פועלת במכשיר Cast.
עכשיו אפשר להתחיל לבנות על בסיס פרויקט המתחילים באמצעות Android Studio:
- בוחרים את הספרייה

app-startמתוך קוד הדוגמה שהורדתם (בוחרים באפשרות ייבוא פרויקט במסך הפתיחה או בתפריט קובץ > חדש > ייבוא פרויקט...). - לוחצים על הלחצן
Sync Project with Gradle Files (סנכרון הפרויקט עם קובצי Gradle). - לוחצים על הלחצן
Run כדי להריץ את האפליקציה ולבדוק את ממשק המשתמש.
עיצוב אפליקציות
האפליקציה מאחזרת רשימה של סרטונים משרת אינטרנט מרוחק ומספקת רשימה למשתמש כדי לעיין בה. המשתמשים יכולים לבחור סרטון כדי לראות את הפרטים שלו או להפעיל אותו באופן מקומי במכשיר הנייד.
האפליקציה מורכבת משתי פעילויות עיקריות: VideoBrowserActivity ו-LocalPlayerActivity. כדי לשלב את הפונקציונליות של Google Cast, האובייקטים מסוג Activities צריכים להיות נגזרים מהאובייקט AppCompatActivity או מאובייקט האב שלו FragmentActivity. המגבלה הזו קיימת כי צריך להוסיף את MediaRouteButton (שמסופק בספריית התמיכה של MediaRouter) כ-MediaRouteActionProvider, וזה יעבוד רק אם הפעילות עוברת בירושה מהמחלקות שצוינו למעלה. ספריית התמיכה MediaRouter תלויה בספריית התמיכה AppCompat שמספקת את המחלקות הנדרשות.
VideoBrowserActivity
הפעילות הזו מכילה Fragment (VideoBrowserFragment). הרשימה הזו מגובה על ידי ArrayAdapter (VideoListAdapter). רשימת הסרטונים והמטא-נתונים המשויכים שלהם מאוחסנים בשרת מרוחק כקובץ JSON. הפקודה AsyncTaskLoader (VideoItemLoader) מאחזרת את ה-JSON הזה ומעבדת אותו כדי ליצור רשימה של אובייקטים מסוג MediaItem.
אובייקט MediaItem מדגים סרטון ואת המטא-נתונים שמשויכים אליו, כמו השם, התיאור, כתובת ה-URL של השידור, כתובת ה-URL של התמונות התומכות ורצועות הטקסט המשויכות (לכתוביות), אם יש כאלה. אובייקט MediaItem מועבר בין פעילויות, ולכן ל-MediaItem יש שיטות עזר להמרה שלו ל-Bundle ולהפך.
כשרכיב הטעינה יוצר את רשימת MediaItems, הוא מעביר את הרשימה הזו אל VideoListAdapter, שמעביר אותה אל VideoBrowserFragment.MediaItems למשתמש מוצגת רשימה של תמונות ממוזערות של סרטונים עם תיאור קצר לכל סרטון. כשבוחרים פריט, ה-MediaItem המתאים מומר ל-Bundle ומועבר ל-LocalPlayerActivity.
LocalPlayerActivity
בפעילות הזו מוצגים המטא-נתונים של סרטון מסוים, והיא מאפשרת למשתמש להפעיל את הסרטון באופן מקומי במכשיר הנייד.
בפעילות מוצג VideoView, ממשק השליטה במדיה ואזור טקסט שבו מוצג תיאור הסרטון שנבחר. הנגן מכסה את החלק העליון של המסך, ומשאיר מקום לתיאור המפורט של הסרטון מתחתיו. המשתמש יכול להפעיל או להשהות את ההפעלה המקומית של סרטונים, או להריץ אותה קדימה.
תלויות
מכיוון שאנחנו משתמשים ב-AppCompatActivity, אנחנו צריכים את ספריית התמיכה של AppCompat. כדי לנהל את רשימת הסרטונים ולקבל את התמונות לרשימה באופן אסינכרוני, אנחנו משתמשים בספריית Volley.
שאלות נפוצות
5. הוספת הכפתור להפעלת Cast

באפליקציה תומכת Cast, הכפתור להפעלת Cast מוצג בכל אחת מהפעילויות שלה. כשלוחצים על הכפתור להפעלת Cast, מוצגת רשימה של מכשירי Cast שהמשתמש יכול לבחור מתוכה. אם המשתמש הפעיל תוכן באופן מקומי במכשיר השולח, בחירה במכשיר Cast תתחיל או תמשיך את ההפעלה במכשיר ה-Cast. במהלך סשן Cast, המשתמש יכול ללחוץ על הכפתור להפעלת Cast ולהפסיק את ההעברה של האפליקציה למכשיר Cast. המשתמש צריך להיות מסוגל להתחבר למכשיר Cast או להתנתק ממנו בזמן שהוא מבצע פעילות כלשהי באפליקציה, כמו שמתואר ברשימת המשימות לעיצוב של Google Cast.
תלויות
מעדכנים את קובץ build.gradle של האפליקציה כדי לכלול את יחסי התלות הנדרשים של הספריות:
dependencies {
implementation 'androidx.appcompat:appcompat:1.5.0'
implementation 'androidx.mediarouter:mediarouter:1.3.1'
implementation 'androidx.recyclerview:recyclerview:1.2.1'
implementation 'com.google.android.gms:play-services-cast-framework:21.1.0'
implementation 'com.android.volley:volley:1.2.1'
implementation "androidx.core:core-ktx:1.8.0"
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8:$kotlin_version"
}
מסנכרנים את הפרויקט כדי לוודא שה-build של הפרויקט מתבצע ללא שגיאות.
אתחול
ל-Cast Framework יש אובייקט singleton גלובלי, CastContext, שמתאם את כל האינטראקציות עם Cast.
כדי לספק את CastOptions שנדרש לאתחול של singleton CastContext, צריך להטמיע את הממשק OptionsProvider. האפשרות הכי חשובה היא מזהה אפליקציית המקלט, שמשמשת לסינון תוצאות החיפוש של מכשיר Cast ולהפעלת אפליקציית המקלט כשמתחיל סשן Cast.
כשמפתחים אפליקציה משלכם תומכת ב-Cast, צריך להירשם כמפתחי Cast ולקבל מזהה אפליקציה לאפליקציה. בסדנת התכנות הזו נשתמש במזהה אפליקציה לדוגמה.
מוסיפים את קובץ CastOptionsProvider.kt החדש הבא לחבילת com.google.sample.cast.refplayer של הפרויקט:
package com.google.sample.cast.refplayer
import android.content.Context
import com.google.android.gms.cast.framework.OptionsProvider
import com.google.android.gms.cast.framework.CastOptions
import com.google.android.gms.cast.framework.SessionProvider
class CastOptionsProvider : OptionsProvider {
override fun getCastOptions(context: Context): CastOptions {
return CastOptions.Builder()
.setReceiverApplicationId(context.getString(R.string.app_id))
.build()
}
override fun getAdditionalSessionProviders(context: Context): List<SessionProvider>? {
return null
}
}
עכשיו צריך להצהיר על OptionsProvider בתוך התג application בקובץ AndroidManifest.xml של האפליקציה:
<meta-data
android:name="com.google.android.gms.cast.framework.OPTIONS_PROVIDER_CLASS_NAME"
android:value="com.google.sample.cast.refplayer.CastOptionsProvider" />
מפעילים את CastContext ב-method VideoBrowserActivity onCreate:
import com.google.android.gms.cast.framework.CastContext
private var mCastContext: CastContext? = null
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.video_browser)
setupActionBar()
mCastContext = CastContext.getSharedInstance(this)
}
מוסיפים את אותה לוגיקת אתחול ל-LocalPlayerActivity.
כפתור הפעלת Cast
עכשיו, אחרי שהרכיב CastContext אותחל, צריך להוסיף את הכפתור להפעלת Cast כדי לאפשר למשתמש לבחור מכשיר Cast. הכפתור להפעלת Cast מיושם על ידי MediaRouteButton מספריית התמיכה MediaRouter. כמו כל סמל של פעולה שאפשר להוסיף לפעילות (באמצעות ActionBar או Toolbar), קודם צריך להוסיף את הפריט המתאים לתפריט.
עורכים את קובץ res/menu/browse.xml ומוסיפים את הפריט MediaRouteActionProvider לתפריט לפני פריט ההגדרות:
<item
android:id="@+id/media_route_menu_item"
android:title="@string/media_route_menu_title"
app:actionProviderClass="androidx.mediarouter.app.MediaRouteActionProvider"
app:showAsAction="always"/>
מחליפים את השיטה onCreateOptionsMenu() של VideoBrowserActivity באמצעות CastButtonFactory כדי לחבר את MediaRouteButton ל-Cast framework:
import com.google.android.gms.cast.framework.CastButtonFactory
private var mediaRouteMenuItem: MenuItem? = null
override fun onCreateOptionsMenu(menu: Menu): Boolean {
super.onCreateOptionsMenu(menu)
menuInflater.inflate(R.menu.browse, menu)
mediaRouteMenuItem = CastButtonFactory.setUpMediaRouteButton(getApplicationContext(), menu,
R.id.media_route_menu_item)
return true
}
אפשר לבטל את ההגדרה של onCreateOptionsMenu ב-LocalPlayerActivity באותו אופן.
לוחצים על הלחצן
Run כדי להריץ את האפליקציה במכשיר הנייד. אמור להופיע הכפתור להפעלת Cast בסרגל הפעולות של האפליקציה. כשלוחצים עליו, מוצגת רשימה של מכשירי Cast ברשת המקומית. גילוי המכשירים מנוהל באופן אוטומטי על ידי CastContext. בוחרים את מכשיר ה-Cast, ואפליקציית המקלט לדוגמה תיטען במכשיר ה-Cast. אתם יכולים לעבור בין פעילות הגלישה לבין פעילות הנגן המקומי, והמצב של הכפתור להפעלת Cast נשאר מסונכרן.
עדיין לא הוספנו תמיכה בהפעלת מדיה, כך שאי אפשר להפעיל סרטונים במכשיר Cast. לוחצים על הכפתור להפעלת Cast כדי להתנתק.
6. העברה (cast) של תוכן סרטון

נרחיב את אפליקציית הדוגמה כך שתכלול גם הפעלת סרטונים מרחוק במכשיר Cast. כדי לעשות את זה, אנחנו צריכים להאזין לאירועים השונים שנוצרים על ידי Cast Framework.
הפעלת Cast של מדיה
ככלל, כדי להפעיל מדיה במכשיר Cast, צריך לבצע את הפעולות הבאות:
- יוצרים אובייקט
MediaInfoשמדמה פריט מדיה. - מתחברים למכשיר Cast ומפעילים את אפליקציית המקלט.
- טוענים את אובייקט
MediaInfoבמכשיר המקבל ומפעילים את התוכן. - מעקב אחרי סטטוס המדיה.
- שליחת פקודות הפעלה למקבל על סמך אינטראקציות של משתמשים.
כבר ביצענו את שלב 2 בקטע הקודם. קל לבצע את שלב 3 באמצעות Cast Framework. בשלב 1 ממפים אובייקט אחד לאובייקט אחר. MediaInfo הוא אובייקט שמסגרת Cast מבינה, ו-MediaItem הוא אובייקט האנקפסולציה של האפליקציה שלנו לקובץ מדיה. אפשר למפות בקלות MediaItem ל-MediaInfo.
אפליקציית הדוגמה LocalPlayerActivity כבר מבחינה בין הפעלה מקומית להפעלה מרחוק באמצעות ה-enum הזה:
private var mLocation: PlaybackLocation? = null
enum class PlaybackLocation {
LOCAL, REMOTE
}
enum class PlaybackState {
PLAYING, PAUSED, BUFFERING, IDLE
}
ב-Codelab הזה לא חשוב שתבינו בדיוק איך פועלת כל הלוגיקה של השחקן לדוגמה. חשוב להבין שצריך לשנות את נגן המדיה באפליקציה כדי שיהיה מודע לשני מיקומי ההפעלה באופן דומה.
בשלב הזה, הנגן המקומי תמיד נמצא במצב הפעלה מקומי, כי הוא עדיין לא יודע דבר על מצבי ההעברה. אנחנו צריכים לעדכן את ממשק המשתמש על סמך מעברי מצב שמתרחשים ב-Cast Framework. לדוגמה, אם מתחילים להפעיל Cast, צריך להפסיק את ההפעלה המקומית ולהשבית חלק מהאמצעים לשליטה בהפעלה. באופן דומה, אם נפסיק את ה-Cast כשאנחנו בפעילות הזו, נצטרך לעבור להפעלה מקומית. כדי לטפל בזה, צריך להאזין לאירועים השונים שנוצרים על ידי Cast Framework.
ניהול סשנים של Cast
במסגרת Cast, סשן Cast משלב את השלבים של התחברות למכשיר, הפעלה (או הצטרפות), התחברות לאפליקציית מקלט ואתחול של ערוץ בקרת מדיה, אם רלוונטי. ערוץ בקרת המדיה הוא האופן שבו מסגרת Cast שולחת ומקבלת הודעות מנגן המדיה של המקלט.
הפעלת Cast תתחיל באופן אוטומטי כשהמשתמש יבחר מכשיר מהכפתור להפעלת Cast, ותיפסק באופן אוטומטי כשהמשתמש ינתק את החיבור. ה-Cast SDK מטפל אוטומטית גם בחיבור מחדש לסשן של מקלט בגלל בעיות ברשת.
נוסיף SessionManagerListener לLocalPlayerActivity:
import com.google.android.gms.cast.framework.CastSession
import com.google.android.gms.cast.framework.SessionManagerListener
...
private var mSessionManagerListener: SessionManagerListener<CastSession>? = null
private var mCastSession: CastSession? = null
...
private fun setupCastListener() {
mSessionManagerListener = object : SessionManagerListener<CastSession> {
override fun onSessionEnded(session: CastSession, error: Int) {
onApplicationDisconnected()
}
override fun onSessionResumed(session: CastSession, wasSuspended: Boolean) {
onApplicationConnected(session)
}
override fun onSessionResumeFailed(session: CastSession, error: Int) {
onApplicationDisconnected()
}
override fun onSessionStarted(session: CastSession, sessionId: String) {
onApplicationConnected(session)
}
override fun onSessionStartFailed(session: CastSession, error: Int) {
onApplicationDisconnected()
}
override fun onSessionStarting(session: CastSession) {}
override fun onSessionEnding(session: CastSession) {}
override fun onSessionResuming(session: CastSession, sessionId: String) {}
override fun onSessionSuspended(session: CastSession, reason: Int) {}
private fun onApplicationConnected(castSession: CastSession) {
mCastSession = castSession
if (null != mSelectedMedia) {
if (mPlaybackState == PlaybackState.PLAYING) {
mVideoView!!.pause()
loadRemoteMedia(mSeekbar!!.progress, true)
return
} else {
mPlaybackState = PlaybackState.IDLE
updatePlaybackLocation(PlaybackLocation.REMOTE)
}
}
updatePlayButton(mPlaybackState)
invalidateOptionsMenu()
}
private fun onApplicationDisconnected() {
updatePlaybackLocation(PlaybackLocation.LOCAL)
mPlaybackState = PlaybackState.IDLE
mLocation = PlaybackLocation.LOCAL
updatePlayButton(mPlaybackState)
invalidateOptionsMenu()
}
}
}
בפעילות LocalPlayerActivity, אנחנו רוצים לקבל הודעה כשאנחנו מתחברים למכשיר Cast או מתנתקים ממנו, כדי שנוכל לעבור לנגן המקומי או ממנו. חשוב לזכור שהקישוריות יכולה להיפסק לא רק בגלל המופע של האפליקציה שפועל במכשיר הנייד, אלא גם בגלל מופע אחר של האפליקציה שלכם (או של אפליקציה אחרת) שפועל במכשיר נייד אחר.
אפשר לגשת לסשן הפעיל הנוכחי בתור SessionManager.getCurrentSession(). הסשנים נוצרים ומוסרים באופן אוטומטי בתגובה לאינטראקציות של המשתמשים עם תיבות הדו-שיח של Cast.
צריך לרשום את מאזין הסשנים ולאתחל כמה משתנים שנשתמש בהם בפעילות. משנים את השיטה LocalPlayerActivity onCreate ל:
import com.google.android.gms.cast.framework.CastContext
...
private var mCastContext: CastContext? = null
...
override fun onCreate(savedInstanceState: Bundle?) {
...
mCastContext = CastContext.getSharedInstance(this)
mCastSession = mCastContext!!.sessionManager.currentCastSession
setupCastListener()
...
loadViews()
...
val bundle = intent.extras
if (bundle != null) {
....
if (shouldStartPlayback) {
....
} else {
if (mCastSession != null && mCastSession!!.isConnected()) {
updatePlaybackLocation(PlaybackLocation.REMOTE)
} else {
updatePlaybackLocation(PlaybackLocation.LOCAL)
}
mPlaybackState = PlaybackState.IDLE
updatePlayButton(mPlaybackState)
}
}
...
}
המדיה בטעינה
ב-Cast SDK, RemoteMediaClient מספק קבוצה של ממשקי API נוחים לניהול הפעלת מדיה מרחוק במקבל. במקרה של CastSession שתומך בהפעלת מדיה, המערכת תיצור באופן אוטומטי מופע של RemoteMediaClient באמצעות ה-SDK. אפשר לגשת אליה באמצעות קריאה לשיטה getRemoteMediaClient() במופע CastSession. מוסיפים את השיטות הבאות ל-LocalPlayerActivity כדי לטעון את הסרטון שנבחר כרגע במכשיר המקבל:
import com.google.android.gms.cast.framework.media.RemoteMediaClient
import com.google.android.gms.cast.MediaInfo
import com.google.android.gms.cast.MediaLoadOptions
import com.google.android.gms.cast.MediaMetadata
import com.google.android.gms.common.images.WebImage
import com.google.android.gms.cast.MediaLoadRequestData
private fun loadRemoteMedia(position: Int, autoPlay: Boolean) {
if (mCastSession == null) {
return
}
val remoteMediaClient = mCastSession!!.remoteMediaClient ?: return
remoteMediaClient.load( MediaLoadRequestData.Builder()
.setMediaInfo(buildMediaInfo())
.setAutoplay(autoPlay)
.setCurrentTime(position.toLong()).build())
}
private fun buildMediaInfo(): MediaInfo? {
val movieMetadata = MediaMetadata(MediaMetadata.MEDIA_TYPE_MOVIE)
mSelectedMedia?.studio?.let { movieMetadata.putString(MediaMetadata.KEY_SUBTITLE, it) }
mSelectedMedia?.title?.let { movieMetadata.putString(MediaMetadata.KEY_TITLE, it) }
movieMetadata.addImage(WebImage(Uri.parse(mSelectedMedia!!.getImage(0))))
movieMetadata.addImage(WebImage(Uri.parse(mSelectedMedia!!.getImage(1))))
return mSelectedMedia!!.url?.let {
MediaInfo.Builder(it)
.setStreamType(MediaInfo.STREAM_TYPE_BUFFERED)
.setContentType("videos/mp4")
.setMetadata(movieMetadata)
.setStreamDuration((mSelectedMedia!!.duration * 1000).toLong())
.build()
}
}
מעדכנים עכשיו שיטות קיימות שונות כדי להשתמש בלוגיקה של סשן Cast לתמיכה בהפעלה מרחוק:
private fun play(position: Int) {
startControllersTimer()
when (mLocation) {
PlaybackLocation.LOCAL -> {
mVideoView!!.seekTo(position)
mVideoView!!.start()
}
PlaybackLocation.REMOTE -> {
mPlaybackState = PlaybackState.BUFFERING
updatePlayButton(mPlaybackState)
//seek to a new position within the current media item's new position
//which is in milliseconds from the beginning of the stream
mCastSession!!.remoteMediaClient?.seek(position.toLong())
}
else -> {}
}
restartTrickplayTimer()
}
private fun togglePlayback() {
...
PlaybackState.IDLE -> when (mLocation) {
...
PlaybackLocation.REMOTE -> {
if (mCastSession != null && mCastSession!!.isConnected) {
loadRemoteMedia(mSeekbar!!.progress, true)
}
}
else -> {}
}
...
}
override fun onPause() {
...
mCastContext!!.sessionManager.removeSessionManagerListener(
mSessionManagerListener!!, CastSession::class.java)
}
override fun onResume() {
Log.d(TAG, "onResume() was called")
mCastContext!!.sessionManager.addSessionManagerListener(
mSessionManagerListener!!, CastSession::class.java)
if (mCastSession != null && mCastSession!!.isConnected) {
updatePlaybackLocation(PlaybackLocation.REMOTE)
} else {
updatePlaybackLocation(PlaybackLocation.LOCAL)
}
super.onResume()
}
בשיטה updatePlayButton, משנים את הערך של המשתנה isConnected:
private fun updatePlayButton(state: PlaybackState?) {
...
val isConnected = (mCastSession != null
&& (mCastSession!!.isConnected || mCastSession!!.isConnecting))
...
}
עכשיו לוחצים על הלחצן
הפעלה כדי להפעיל את האפליקציה בנייד. מתחברים למכשיר Cast ומתחילים להפעיל סרטון. הסרטון אמור להתחיל לפעול במכשיר המקבל.
7. בקר קטן
בצ'קליסט של עיצוב Cast נדרש שכל אפליקציית Cast תספק בקר מיני שמופיע כשהמשתמש עובר מדף התוכן הנוכחי. השלט הקטן מספק גישה מיידית ותזכורת גלויה להפעלה הנוכחית של Cast.

Cast SDK מספק תצוגה בהתאמה אישית, MiniControllerFragment, שאפשר להוסיף לקובץ הפריסה של הפעילויות שבהן רוצים להציג את אמצעי הבקרה המצומצם.
מוסיפים את הגדרת הפרגמנט הבאה לתחתית של res/layout/player_activity.xml ושל res/layout/video_browser.xml:
<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"/>
לוחצים על הלחצן
הפעלה כדי להפעיל את האפליקציה ולהפעיל Cast של סרטון. כשההפעלה תתחיל במכשיר המקבל, אמצעי הבקרה הקטן יופיע בחלק התחתון של כל פעילות. אפשר לשלוט בהפעלה מרחוק באמצעות המיני-בקר. אם עוברים בין פעילות הגלישה לבין פעילות הנגן המקומי, הסטטוס של המיני-בקר צריך להישאר מסונכרן עם סטטוס הפעלת המדיה במכשיר המקבל.
8. התראות ומסך הנעילה
בצ'קליסט של Google Cast יש דרישה שאפליקציית השולח תטמיע ממשק השליטה במדיה מהתראה וממסך הנעילה.

Cast SDK מספק MediaNotificationService כדי לעזור לאפליקציית השולח ליצור ממשק השליטה במדיה עבור ההתראה ומסך הנעילה. השירות משולב אוטומטית במניפסט של האפליקציה באמצעות Gradle.
האפליקציה MediaNotificationService תפעל ברקע כשהשולח יפעיל Cast, ותציג התראה עם תמונה ממוזערת ומטא-נתונים לגבי הפריט הנוכחי שמופעל ב-Cast, לחצן הפעלה/השהיה ולחצן עצירה.
אפשר להפעיל את ההתראות ואת אמצעי הבקרה במסך הנעילה באמצעות CastOptions כשמפעילים את CastContext. כברירת מחדל, ממשק השליטה במדיה מופעל בהתראות ובמסך הנעילה. תכונת מסך הנעילה מופעלת כל עוד ההתראות מופעלות.
עורכים את CastOptionsProvider ומשנים את ההטמעה של getCastOptions כך שתתאים לקוד הזה:
import com.google.android.gms.cast.framework.media.CastMediaOptions
import com.google.android.gms.cast.framework.media.NotificationOptions
override fun getCastOptions(context: Context): CastOptions {
val notificationOptions = NotificationOptions.Builder()
.setTargetActivityClassName(VideoBrowserActivity::class.java.name)
.build()
val mediaOptions = CastMediaOptions.Builder()
.setNotificationOptions(notificationOptions)
.build()
return CastOptions.Builder()
.setReceiverApplicationId(context.getString(R.string.app_id))
.setCastMediaOptions(mediaOptions)
.build()
}
לוחצים על הלחצן
Run כדי להריץ את האפליקציה במכשיר הנייד. מפעילים Cast לסרטון ויוצאים מאפליקציית הדוגמה. אמורה להופיע התראה לגבי הסרטון שמופעל כרגע במקלט. נועלים את המכשיר הנייד, ועכשיו אמצעי הבקרה להפעלת המדיה במכשיר Cast אמורים להופיע במסך הנעילה.

9. שכבת-על של מבצע היכרות
בצ'קליסט של עיצוב Google Cast יש דרישה שאפליקציית השולט תציג את הכפתור להפעלת Cast למשתמשים קיימים כדי ליידע אותם שאפליקציית השולט תומכת עכשיו בהעברה, וגם כדי לעזור למשתמשים חדשים ב-Google Cast.

Cast SDK מספק תצוגה בהתאמה אישית, IntroductoryOverlay, שאפשר להשתמש בה כדי להדגיש את הכפתור להפעלת Cast כשהוא מוצג למשתמשים בפעם הראשונה. מוסיפים את הקוד הבא אל VideoBrowserActivity:
import com.google.android.gms.cast.framework.IntroductoryOverlay
import android.os.Looper
private var mIntroductoryOverlay: IntroductoryOverlay? = null
private fun showIntroductoryOverlay() {
mIntroductoryOverlay?.remove()
if (mediaRouteMenuItem?.isVisible == true) {
Looper.myLooper().run {
mIntroductoryOverlay = com.google.android.gms.cast.framework.IntroductoryOverlay.Builder(
this@VideoBrowserActivity, mediaRouteMenuItem!!)
.setTitleText("Introducing Cast")
.setSingleTime()
.setOnOverlayDismissedListener(
object : IntroductoryOverlay.OnOverlayDismissedListener {
override fun onOverlayDismissed() {
mIntroductoryOverlay = null
}
})
.build()
mIntroductoryOverlay!!.show()
}
}
}
עכשיו מוסיפים CastStateListener ומבצעים קריאה לשיטה showIntroductoryOverlay כשמכשיר Cast זמין, על ידי שינוי השיטה onCreate ודריסת השיטות onResume ו-onPause כך שיתאימו לשיטות הבאות:
import com.google.android.gms.cast.framework.CastState
import com.google.android.gms.cast.framework.CastStateListener
private var mCastStateListener: CastStateListener? = null
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.video_browser)
setupActionBar()
mCastStateListener = object : CastStateListener {
override fun onCastStateChanged(newState: Int) {
if (newState != CastState.NO_DEVICES_AVAILABLE) {
showIntroductoryOverlay()
}
}
}
mCastContext = CastContext.getSharedInstance(this)
}
override fun onResume() {
super.onResume()
mCastContext?.addCastStateListener(mCastStateListener!!)
}
override fun onPause() {
super.onPause()
mCastContext?.removeCastStateListener(mCastStateListener!!)
}
מוחקים את נתוני האפליקציה או מסירים את האפליקציה מהמכשיר. לאחר מכן, לוחצים על הלחצן
Run כדי להריץ את האפליקציה במכשיר הנייד. אמור להופיע מסך הפתיחה (אם הוא לא מופיע, צריך לנקות את נתוני האפליקציה).
10. שלט מורחב
במסגרת רשימת המשימות לעיצוב של Google Cast, אפליקציית השולח צריכה לספק בקר מורחב למדיה שמועברת ב-Cast. הבקר המורחב הוא גרסה של הבקר המוקטן במסך מלא.

Cast SDK מספק ווידג'ט לבקר המורחב שנקרא ExpandedControllerActivity. זוהי מחלקה מופשטת שצריך ליצור ממנה מחלקת משנה כדי להוסיף את הכפתור להפעלת Cast.
קודם יוצרים קובץ משאבים חדש לתפריט בשם expanded_controller.xml עבור בקר מורחב כדי לספק את הכפתור להפעלת Cast:
<?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="@string/media_route_menu_title"
app:actionProviderClass="androidx.mediarouter.app.MediaRouteActionProvider"
app:showAsAction="always"/>
</menu>
יוצרים חבילה חדשה expandedcontrols בחבילה com.google.sample.cast.refplayer. לאחר מכן, יוצרים קובץ חדש בשם ExpandedControlsActivity.kt בחבילה com.google.sample.cast.refplayer.expandedcontrols.
package com.google.sample.cast.refplayer.expandedcontrols
import android.view.Menu
import com.google.android.gms.cast.framework.media.widget.ExpandedControllerActivity
import com.google.sample.cast.refplayer.R
import com.google.android.gms.cast.framework.CastButtonFactory
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
}
}
עכשיו צריך להצהיר על ExpandedControlsActivity ב-AndroidManifest.xml בתוך התג application מעל OPTIONS_PROVIDER_CLASS_NAME:
<application>
...
<activity
android:name="com.google.sample.cast.refplayer.expandedcontrols.ExpandedControlsActivity"
android:label="@string/app_name"
android:launchMode="singleTask"
android:theme="@style/Theme.CastVideosDark"
android:screenOrientation="portrait"
android:exported="true">
<intent-filter>
<action android:name="android.intent.action.MAIN"/>
</intent-filter>
<meta-data
android:name="android.support.PARENT_ACTIVITY"
android:value="com.google.sample.cast.refplayer.VideoBrowserActivity"/>
</activity>
...
</application>
עורכים את CastOptionsProvider ומשנים את NotificationOptions ואת CastMediaOptions כדי להגדיר את פעילות היעד לExpandedControlsActivity:
import com.google.sample.cast.refplayer.expandedcontrols.ExpandedControlsActivity
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()
}
מעדכנים את השיטה LocalPlayerActivity loadRemoteMedia כדי להציג את ExpandedControlsActivity כשהמדיה המרוחקת נטענת:
import com.google.sample.cast.refplayer.expandedcontrols.ExpandedControlsActivity
private fun loadRemoteMedia(position: Int, autoPlay: Boolean) {
if (mCastSession == null) {
return
}
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(buildMediaInfo())
.setAutoplay(autoPlay)
.setCurrentTime(position.toLong()).build())
}
לוחצים על הלחצן
הפעלה כדי להפעיל את האפליקציה במכשיר הנייד ולהפעיל Cast של סרטון. אמור להופיע בקר מורחב. חוזרים לרשימת הסרטונים, וכשלוחצים על המיני-בקר, הבקר המורחב נטען שוב. כדי לראות את ההתראה, צריך לצאת מהאפליקציה. כדי לטעון את הבקר המורחב, לוחצים על תמונת ההתראה.
11. הוספת תמיכה ב-Cast Connect
ספריית Cast Connect מאפשרת לאפליקציות שולחות קיימות לתקשר עם אפליקציות Android TV באמצעות פרוטוקול Cast. Cast Connect מבוסס על תשתית Cast, ואפליקציה ל-Android TV פועלת כמקלט.
תלויות
הערה: כדי להטמיע את Cast Connect, גרסת play-services-cast-framework צריכה להיות 19.0.0 ומעלה.
LaunchOptions
כדי להפעיל את אפליקציית Android TV, שנקראת גם Android Receiver, צריך להגדיר את הדגל setAndroidReceiverCompatible כ-true באובייקט LaunchOptions. אובייקט LaunchOptions הזה קובע איך מפעילים את הרסיבר, והוא מועבר אל CastOptions שמוחזר על ידי המחלקה CastOptionsProvider. הגדרת הדגל שצוין למעלה לערך false תפעיל את מקלט האינטרנט עבור מזהה האפליקציה שהוגדר ב-Cast Developer Console.
מוסיפים את הטקסט הבא לקובץ CastOptionsProvider.kt בשיטה getCastOptions:
import com.google.android.gms.cast.LaunchOptions
...
val launchOptions = LaunchOptions.Builder()
.setAndroidReceiverCompatible(true)
.build()
return new CastOptions.Builder()
.setLaunchOptions(launchOptions)
...
.build()
הגדרת פרטי כניסה להפעלה
בצד השולח, אפשר לציין CredentialsData כדי לייצג את מי שמצטרף להפעלה. credentials היא מחרוזת שאפשר להגדיר אותה, כל עוד אפליקציית ATV יכולה להבין אותה. הפרמטר CredentialsData מועבר לאפליקציה ל-Android TV רק בזמן ההפעלה או ההצטרפות. אם תגדירו אותו מחדש בזמן שאתם מחוברים, הוא לא יועבר לאפליקציה ל-Android TV.
כדי להגדיר את פרטי ההפעלה, צריך להגדיר את CredentialsData ולהעביר אותו לאובייקט LaunchOptions. מוסיפים את הקוד הבא לשיטה getCastOptions בקובץ CastOptionsProvider.kt:
import com.google.android.gms.cast.CredentialsData
...
val credentialsData = CredentialsData.Builder()
.setCredentials("{\"userId\": \"abc\"}")
.build()
val launchOptions = LaunchOptions.Builder()
...
.setCredentialsData(credentialsData)
.build()
הגדרת פרטי כניסה ב-LoadRequest
אם האפליקציה שלכם ל-Web Receiver והאפליקציה ל-Android TV מטפלות ב-credentials בצורה שונה, יכול להיות שתצטרכו להגדיר credentials נפרד לכל אחת מהן. כדי לטפל בבעיה הזו, מוסיפים את הקוד הבא לקובץ LocalPlayerActivity.kt בקטע loadRemoteMedia function:
remoteMediaClient.load(MediaLoadRequestData.Builder()
...
.setCredentials("user-credentials")
.setAtvCredentials("atv-user-credentials")
.build())
בהתאם לאפליקציית המקלט שאליה המכשיר השולח מבצע Cast, ה-SDK יטפל עכשיו באופן אוטומטי בפרטי הכניסה שבהם צריך להשתמש בסשן הנוכחי.
בדיקת Cast Connect
שלבים להתקנת קובץ APK של Android TV ב-Chromecast with Google TV
- מאתרים את כתובת ה-IP של מכשיר Android TV. בדרך כלל, הוא מופיע בקטע הגדרות > רשת ואינטרנט > (שם הרשת שאליה המכשיר מחובר). בצד שמאל יופיעו הפרטים וכתובת ה-IP של המכשיר ברשת.
- משתמשים בכתובת ה-IP של המכשיר כדי להתחבר אליו באמצעות ADB דרך הטרמינל:
$ adb connect <device_ip_address>:5555
- בחלון הטרמינל, עוברים לתיקייה ברמה העליונה של הדוגמאות של ה-codelab שהורדתם בתחילת ה-codelab הזה. לדוגמה:
$ cd Desktop/android_codelab_src
- כדי להתקין את קובץ ה-APK בתיקייה הזו ב-Android TV, מריצים את הפקודה:
$ adb -s <device_ip_address>:5555 install android-tv-app.apk
- עכשיו אמורה להופיע אפליקציה בשם Cast Videos בתפריט Your Apps במכשיר Android TV.
- חוזרים לפרויקט ב-Android Studio ולוחצים על לחצן ההפעלה כדי להתקין ולהפעיל את אפליקציית השולח במכשיר הנייד הפיזי. בפינה השמאלית העליונה, לוחצים על סמל ה-Cast ובוחרים את מכשיר Android TV מבין האפשרויות הזמינות. עכשיו אמורה להופיע אפליקציה ל-Android TV במכשיר Android TV, ואם תפעילו סרטון תוכלו לשלוט בהפעלת הסרטון באמצעות שלט רחוק ל-Android TV.
12. התאמה אישית של ווידג'טים של Cast
אתם יכולים להתאים אישית את ווידג'טים של Cast על ידי הגדרת הצבעים, העיצוב של הלחצנים, הטקסט והתמונה הממוזערת, ובחירת סוגי הלחצנים שיוצגו.
עדכון של res/values/styles_castvideo.xml
<style name="Theme.CastVideosTheme" parent="Theme.AppCompat.Light.NoActionBar">
...
<item name="mediaRouteTheme">@style/CustomMediaRouterTheme</item>
<item name="castIntroOverlayStyle">@style/CustomCastIntroOverlay</item>
<item name="castMiniControllerStyle">@style/CustomCastMiniController</item>
<item name="castExpandedControllerStyle">@style/CustomCastExpandedController</item>
<item name="castExpandedControllerToolbarStyle">
@style/ThemeOverlay.AppCompat.ActionBar
</item>
...
</style>
מגדירים את העיצובים המותאמים אישית הבאים:
<!-- Customize Cast Button -->
<style name="CustomMediaRouterTheme" parent="Theme.MediaRouter">
<item name="mediaRouteButtonStyle">@style/CustomMediaRouteButtonStyle</item>
</style>
<style name="CustomMediaRouteButtonStyle" parent="Widget.MediaRouter.Light.MediaRouteButton">
<item name="mediaRouteButtonTint">#EEFF41</item>
</style>
<!-- Customize Introductory Overlay -->
<style name="CustomCastIntroOverlay" parent="CastIntroOverlay">
<item name="castButtonTextAppearance">@style/TextAppearance.CustomCastIntroOverlay.Button</item>
<item name="castTitleTextAppearance">@style/TextAppearance.CustomCastIntroOverlay.Title</item>
</style>
<style name="TextAppearance.CustomCastIntroOverlay.Button" parent="android:style/TextAppearance">
<item name="android:textColor">#FFFFFF</item>
</style>
<style name="TextAppearance.CustomCastIntroOverlay.Title" parent="android:style/TextAppearance.Large">
<item name="android:textColor">#FFFFFF</item>
</style>
<!-- Customize Mini Controller -->
<style name="CustomCastMiniController" parent="CastMiniController">
<item name="castShowImageThumbnail">true</item>
<item name="castTitleTextAppearance">@style/TextAppearance.AppCompat.Subhead</item>
<item name="castSubtitleTextAppearance">@style/TextAppearance.AppCompat.Caption</item>
<item name="castBackground">@color/accent</item>
<item name="castProgressBarColor">@color/orange</item>
</style>
<!-- Customize Expanded Controller -->
<style name="CustomCastExpandedController" parent="CastExpandedController">
<item name="castButtonColor">#FFFFFF</item>
<item name="castPlayButtonDrawable">@drawable/cast_ic_expanded_controller_play</item>
<item name="castPauseButtonDrawable">@drawable/cast_ic_expanded_controller_pause</item>
<item name="castStopButtonDrawable">@drawable/cast_ic_expanded_controller_stop</item>
</style>
13. מזל טוב
עכשיו אתם יודעים איך להוסיף תמיכה ב-Cast לאפליקציית וידאו באמצעות הווידג'טים של Cast SDK ב-Android.
פרטים נוספים זמינים במדריך למפתחים בנושא שליחה ב-Android.