1. סקירה כללית
בקודלאב הזה תלמדו איך לשנות אפליקציית וידאו קיימת ל-Android כדי להעביר תוכן למכשיר שתומך ב-Google Cast.
מה זה Google Cast?
Google Cast מאפשר למשתמשים להעביר תוכן מהנייד לטלוויזיה. לאחר מכן, המשתמשים יכולים להשתמש בנייד שלהם כשלט רחוק להפעלת מדיה בטלוויזיה.
Google Cast SDK מאפשר לכם להרחיב את האפליקציה כדי לשלוט בטלוויזיה או במערכת סאונד. ה-SDK של Cast מאפשר לך להוסיף את רכיבי ממשק המשתמש הנחוצים על סמך רשימת המשימות לעיצוב של Google Cast.
רשימת המשימות לעיצוב של Google Cast מסופקת כדי להפוך את חוויית המשתמש של Cast לפשוטה וצפויה בכל הפלטפורמות הנתמכות.
מה אנחנו הולכים לבנות?
בסיום הקודלאב הזה, תהיה לכם אפליקציית וידאו ל-Android שתוכלו להעביר (cast) באמצעותה סרטונים למכשיר שתומך ב-Google Cast.
מה תלמדו
- איך מוסיפים את Google Cast SDK לאפליקציית וידאו לדוגמה.
- איך מוסיפים את לחצן ההעברה כדי לבחור מכשיר Google Cast.
- איך מתחברים למכשיר Cast ומפעילים מקלט מדיה.
- איך מעבירים סרטון.
- איך מוסיפים לוח בקרה של Cast Mini לאפליקציה.
- איך לתמוך בהתראות מדיה ובפקדים במסך הנעילה.
- איך להוסיף בקר מורחב.
- איך להציג שכבת-על של מבוא.
- איך מתאימים אישית ווידג'טים של Cast.
- איך משלבים את Cast Connect
מה נדרש
- Android SDK בגרסה העדכנית ביותר.
- Android Studio מגרסה 3.2 ואילך
- מכשיר נייד אחד עם Android 4.1 ואילך (Jelly Bean, API ברמה 16).
- כבל USB להעברת נתונים לחיבור המכשיר הנייד למחשב הפיתוח.
- מכשיר Google 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
מתיקיית הקוד לדוגמה ולוחצים על 'אישור'.
לוחצים על File (קובץ) > Sync Project with Gradle Files (סנכרון הפרויקט עם קובצי Gradle).
מפעילים ניפוי באגים ב-USB במכשיר Android – ב-Android מגרסה 4.2 ואילך, מסך האפשרויות למפתחים מוסתר כברירת מחדל. כדי להציג אותו, עוברים אל הגדרות > מידע על הטלפון ומקישים על מספר Build שבע פעמים. חוזרים למסך הקודם, עוברים אל מערכת > מתקדם ומקישים על אפשרויות למפתחים בתחתית המסך. לאחר מכן, מקישים על ניפוי באגים ב-USB כדי להפעיל אותה.
מחברים את מכשיר Android ולוחצים על הלחצן Run ב-Android Studio. אחרי כמה שניות אמורה להופיע אפליקציית הווידאו העברת סרטונים.
לוחצים על הלחצן להפעלת Cast באפליקציית הווידאו ובוחרים את מכשיר Google Cast.
בוחרים סרטון ולוחצים על לחצן ההפעלה.
הסרטון יתחיל לפעול במכשיר Google Cast.
הבקר המורחב יוצג. אפשר להשתמש בלחצן ההפעלה/ההשהיה כדי לשלוט בהפעלה.
חוזרים לרשימת הסרטונים.
אמצעי בקרה מיניאטורי יופיע עכשיו בתחתית המסך.
לוחצים על לחצן ההשהיה בשלט המיני כדי להשהות את הסרטון במכשיר הקולט. לוחצים על לחצן ההפעלה בנגן המיני כדי להמשיך את הפעלת הסרטון.
לוחצים על הלחצן הראשי בנייד. מושכים למטה את ההתראות ועכשיו אמורה להופיע התראה לגבי סשן ההעברה (cast).
נועלים את הטלפון. כשמבטלים את הנעילה, אמורה להופיע התראה במסך הנעילה כדי לשלוט בהפעלת המדיה או להפסיק את ההעברה (cast).
חוזרים לאפליקציית הווידאו ולוחצים על לחצן ההעברה כדי להפסיק את ההעברה במכשיר Google Cast.
שאלות נפוצות
4. הכנת פרויקט ההתחלה
אנחנו צריכים להוסיף תמיכה ב-Google Cast לאפליקציה שהורדת. ריכזנו כאן כמה מונחים של Google Cast שבהם נשתמש במהלך הקודלאב:
- אפליקציית שליחת הודעות שפועלת במכשיר נייד או במחשב נייד,
- אפליקציית מקלט פועלת במכשיר Google Cast.
עכשיו אתם מוכנים לפתח את הפרויקט באמצעות Android Studio:
- בוחרים את הספרייה
app-start
מתוך הורדת הקוד לדוגמה (בוחרים באפשרות ייבוא פרויקט במסך הפתיחה או באפשרות בתפריט קובץ > חדש > ייבוא פרויקט...). - לוחצים על הלחצן Sync Project with Gradle Files.
- לוחצים על הלחצן Run כדי להריץ את האפליקציה ולעיין בממשק המשתמש.
עיצוב אפליקציות
האפליקציה מאחזרת רשימה של סרטונים משרת אינטרנט מרוחק ומספקת רשימה למשתמש לגלישה. המשתמשים יכולים לבחור סרטון כדי לראות את הפרטים שלו או להפעיל אותו באופן מקומי במכשיר הנייד.
האפליקציה מורכבת משתי פעילויות עיקריות: VideoBrowserActivity
ו-LocalPlayerActivity
. כדי לשלב את הפונקציונליות של Google Cast, הפעילויות צריכות לקבל בירושה מה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
, שמציג את רשימת MediaItems
ב-VideoBrowserFragment
. המשתמש רואה רשימה של תמונות ממוזערות של סרטונים עם תיאור קצר של כל סרטון. כשבוחרים פריט, ה-MediaItem
התואם מומר ל-Bundle
ומוענק ל-LocalPlayerActivity
.
LocalPlayerActivity
הפעילות הזו מציגה את המטא-נתונים של סרטון מסוים ומאפשרת למשתמש להפעיל את הסרטון באופן מקומי במכשיר הנייד.
הפעילות כוללת את VideoView
, כמה פקדי מדיה ואזור טקסט שבו מוצג התיאור של הסרטון שנבחר. הנגן מכסה את החלק העליון של המסך, ומשאיר מקום לתיאור המפורט של הסרטון מתחתיו. המשתמש יכול להפעיל/להשהות או לבקש הפעלה מקומית של סרטונים.
יחסי תלות
בגלל שאנחנו משתמשים ב-AppCompatActivity
, אנחנו זקוקים לספריית התמיכה של AppCompat. כדי לנהל את רשימת הסרטונים ולקבל את התמונות של הרשימה באופן אסינכררוני, אנחנו משתמשים בספרייה Volley.
שאלות נפוצות
5. הוספת הלחצן להפעלת 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 יש אובייקט singleton גלובלי, CastContext
, שמרכז את כל האינטראקציות עם Cast.
עליכם להטמיע את הממשק OptionsProvider
כדי לספק את ה-CastOptions
הדרוש לאתחול הסינגלטון CastContext
. האפשרות החשובה ביותר היא מזהה האפליקציה של הנמען, שמשמשים לסינון תוצאות החיפוש של מכשירי Cast ולהפעלת אפליקציית הנמען כשמתחילים סשן העברה.
כשמפתחים אפליקציה משלכם שתומכת ב-Cast, צריך להירשם כמפתח Cast ולקבל מזהה אפליקציה לאפליקציה. ב-Codelab הזה נשתמש במזהה אפליקציה לדוגמה.
מוסיפים את קובץ 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
במתודה 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 מוטמע על ידי 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
ל-framework של Cast:
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 frame
העברת מדיה
באופן כללי, כדי להפעיל מדיה במכשיר Cast, צריך לבצע את הפעולות הבאות:
- יוצרים אובייקט
MediaInfo
שמתאר פריט מדיה. - מתחברים למכשיר ההעברה (cast) ופותחים את אפליקציית המקלט.
- טוענים את האובייקט
MediaInfo
למכשיר המקבל ומפעילים את התוכן. - מעקב אחר סטטוס המדיה.
- שליחת פקודות הפעלה למכשיר הקולט על סמך אינטראקציות של משתמשים.
כבר ביצענו את שלב 2 בקטע הקודם. קל לבצע את שלב 3 באמצעות מסגרת Cast. שלב 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). אנחנו צריכים לעדכן את ממשק המשתמש על סמך מעברי מצבים שמתרחשים ב-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
שתומך בהפעלת מדיה, ה-SDK ייצור באופן אוטומטי מופע של RemoteMediaClient
. אפשר לגשת ל-method 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 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"/>
לוחצים על הלחצן הפעלה כדי להריץ את האפליקציה ולהעביר סרטון. כשהפעלת ההפעלה תתחיל במכשיר הקולט, אמור להופיע אמצעי הבקרה המינימלי בחלק התחתון של כל פעילות. אפשר לשלוט בהפעלה מרחוק באמצעות המיני-בקר. אם מנווטים בין פעילות העיון לפעילות הנגן המקומית, מצב המיני של הבקר אמור להישאר מסונכרן עם סטטוס הפעלת המדיה במקלט.
8. התראות ומסך נעילה
לפי רשימת המשימות לעיצוב של Google Cast, אפליקציית השליחה צריכה ליישם אמצעי בקרה על מדיה מהתראה וממסך הנעילה.
Cast SDK מספק MediaNotificationService
כדי לעזור לאפליקציית השולח ליצור אמצעי בקרה על המדיה בהודעה ובמסך הנעילה. השירות ימוזג באופן אוטומטי למניפסט של האפליקציה על ידי gradle.
MediaNotificationService
יפעל ברקע כשהשולח מעביר תוכן, ויציג התראה עם תמונה ממוזערת של התמונה ומטא-נתונים לגבי הפריט הנוכחי שניתן להפעיל 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 כדי להריץ את האפליקציה בנייד. מעבירים סרטון ומנווטים מחוץ לאפליקציה לדוגמה. אמורה להופיע התראה על הסרטון שפועל כרגע במכשיר המקבל. נועלים את המכשיר הנייד, ומסך הנעילה אמור להציג עכשיו את הפקדים להפעלת המדיה במכשיר ההעברה.
9. שכבת-על של מבצע היכרות
כדי להשתמש ברשימת המשימות לעיצוב של Google Cast, יש צורך באפליקציית שולח שתציג את לחצן הפעלת Cast למשתמשים קיימים, כדי להודיע להם שאפליקציית השולח תומכת עכשיו בהעברה וגם עוזרת למשתמשים חדשים ב-Google Cast.
ערכת ה-SDK של Cast מספקת תצוגה מותאמת אישית, 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
וקוראים ל-method 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 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())
}
לוחצים על הלחצן הפעלה כדי להפעיל את האפליקציה במכשיר הנייד ולהעביר סרטון. אתם אמורים לראות את השלט הרחוק המורחב. חוזרים לרשימת הסרטונים, ולוחצים על בקר המיני. בקר המיני המורחב ייטען שוב. עוברים מהאפליקציה כדי לראות את ההתראה. לוחצים על התמונה של ההתראה כדי לטעון את הבקר המורחב.
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.
מוסיפים את הטקסט הבא לשיטה getCastOptions
בקובץ CastOptionsProvider.kt
:
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
אם האפליקציה של מקלט האינטרנט והאפליקציה ל-Android TV מטפלות ב-credentials
באופן שונה, יכול להיות שתצטרכו להגדיר credentials
נפרד לכל אחת מהן. כדי לטפל בבעיה הזו, מוסיפים את הקוד הבא לקובץ LocalPlayerActivity.kt
, בפונקציה loadRemoteMedia
:
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
- עכשיו אמורה להופיע אפליקציה בשם העברת סרטונים בתפריט האפליקציות שלך במכשיר Android TV.
- חוזרים לפרויקט ב-Android Studio ולוחצים על לחצן ההפעלה כדי להתקין ולהפעיל את אפליקציית השליחה בנייד הפיזי. בפינה השמאלית העליונה, לוחצים על סמל ההעברה ובוחרים את מכשיר 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 Sender.