הפעלת העברה (cast) של אפליקציית אינטרנט

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

הלוגו של Google Cast

בקודלאב הזה תלמדו איך לשנות אפליקציית וידאו קיימת לאינטרנט כדי להעביר תוכן למכשיר שתומך ב-Google Cast.

מה זה Google Cast?

Google Cast מאפשר למשתמשים להעביר תוכן מהנייד לטלוויזיה. המשתמשים יוכלו להשתמש בנייד שלהם כשלט רחוק להפעלת מדיה בטלוויזיה.

Google Cast SDK מאפשר לכם להרחיב את האפליקציה כדי לשלוט בטלוויזיה או במערכת סאונד. באמצעות Cast SDK אפשר להוסיף את רכיבי ממשק המשתמש הנדרשים על סמך רשימת המשימות לעיצוב של Google Cast.

רשימת המשימות לעיצוב של Google Cast מסופקת כדי להפוך את חוויית המשתמש של Cast לפשוטה וצפויה בכל הפלטפורמות הנתמכות.

מה אנחנו הולכים ליצור?

בסיום ה-Codelab הזה, תהיה לך אפליקציית וידאו של Chrome, שדרכה אפשר להעביר סרטונים למכשיר Google Cast.

מה תלמדו

  • איך מוסיפים את Google Cast SDK לאפליקציית וידאו לדוגמה.
  • איך מוסיפים את לחצן ההעברה כדי לבחור מכשיר Google Cast.
  • איך מתחברים למכשיר Cast ומפעילים מקלט מדיה.
  • איך מעבירים סרטון.
  • איך משלבים את Cast Connect

מה נדרש

  • דפדפן Google Chrome בגרסה העדכנית ביותר.
  • שירות אירוח HTTPS, כמו Firebase Hosting או ngrok.
  • מכשיר Google Cast, כמו Chromecast או Android TV, עם הגדרת גישה לאינטרנט.
  • טלוויזיה או צג עם כניסת HDMI.
  • כדי לבדוק את השילוב של Cast Connect, נדרש מכשיר Chromecast with Google TV, אבל הוא לא נדרש בשאר השלבים של Codelab. אם אין לכם מכשיר כזה, אתם יכולים לדלג על השלב הוספת תמיכה ב-Cast Connect לקראת סוף המדריך הזה.

ניסיון

  • נדרש ידע קודם בפיתוח אתרים.
  • בנוסף, נדרשת לך ניסיון קודם בצפייה בטלוויזיה :)

איך תוכלו להשתמש במדריך הזה?

לקרוא את המאמר לקרוא את המאמר ולבצע את התרגילים

מה מידת הניסיון שלך בפיתוח אפליקציות אינטרנט?

מתחילים בינוניים מומחים

איזה דירוג מגיע לדעתך לחוויית הצפייה בטלוויזיה?

מתחילים בינוניים מומחים

2. לקבלת הקוד לדוגמה

אתם יכולים להוריד את כל הקוד לדוגמה למחשב...

ופותחים את קובץ ה-ZIP שהורדתם.

3. הרצת האפליקציה לדוגמה

הלוגו של Google Chrome

קודם נראה איך נראית אפליקציית הדוגמה המושלמת. האפליקציה היא נגן וידאו בסיסי. המשתמש יכול לבחור סרטון מתוך רשימה, ואז להפעיל אותו באופן מקומי במכשיר או להעביר אותו (cast) למכשיר Google Cast.

כדי שתוכלו להשתמש ב-PDF המוגמר, צריך לארח אותו.

אם אין לכם שרת זמין לשימוש, תוכלו להשתמש באירוח ב-Firebase או ב-ngrok.

הפעלת השרת

אחרי שמגדירים את השירות שבחרתם, עוברים אל app-done ומפעילים את השרת.

בדפדפן, נכנסים לכתובת ה-URL מסוג https של הדוגמה שאירחתם.

  1. האפליקציה של הסרטון אמורה להופיע.
  2. לוחצים על הלחצן להפעלת Cast ובוחרים את מכשיר ה-Google Cast.
  3. בוחרים סרטון ולוחצים על לחצן ההפעלה.
  4. הסרטון יתחיל לפעול במכשיר Google Cast.

תמונה של סרטון שפועל במכשיר Cast

אפשר ללחוץ על לחצן ההשהיה ברכיב הווידאו כדי להשהות את הסרטון במקלט. לוחצים על לחצן ההפעלה ברכיב הווידאו כדי להמשיך את ההפעלה של הסרטון.

כדי להפסיק את ההעברה למכשיר Google Cast, לוחצים על הלחצן להפעלת Cast.

לפני שנמשיך, צריך להפסיק את השרת.

4. הכנת פרויקט ההתחלה

תמונה של סרטון שפועל במכשיר Cast

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

  • אפליקציית שליחה שפועלת במכשיר נייד או במחשב נייד,
  • אפליקציית מקלט פועלת במכשיר Google Cast.

עכשיו אתם מוכנים להמשיך לפתח את הפרויקט באמצעות עורך הטקסט המועדף עליכם:

  1. בוחרים את הספרייה סמל התיקייהapp-start מההורדה של קוד לדוגמה.
  2. מריצים את האפליקציה באמצעות השרת ומעיינים בממשק המשתמש.

הערה: במהלך העבודה ב-codelab הזה, תצטרכו לארח מחדש את הדוגמה בשרת שלכם, בהתאם לשירות.

עיצוב אפליקציות

האפליקציה מאחזרת רשימה של סרטונים משרת אינטרנט מרוחק ומספקת רשימה למשתמש לגלישה. המשתמשים יכולים לבחור סרטון כדי לראות את הפרטים שלו או להפעיל אותו באופן מקומי במכשיר הנייד.

האפליקציה מורכבת מתצוגה ראשית אחת, שמוגדרת ב-index.html והבקר הראשי, CastVideos.js.

index.html

קובץ ה-HTML הזה מכיל את ההצהרה על כמעט כל ממשק המשתמש של אפליקציית האינטרנט.

יש כמה קטעים של צפיות, יש לנו את div#main_video, שמכיל את רכיב הסרטון. בקשר ל-div של הסרטון, יש לנו את div#media_control, שמגדיר את כל אמצעי הבקרה של רכיב הווידאו. מתחתיו מופיע media_info, שמציג את פרטי הסרטון בתצוגה. לבסוף, ה-div‏ carousel מציג רשימה של סרטונים ב-div.

גם הקובץ index.html מבצע אתחול של Cast SDK, ומורה לפונקציה CastVideos להיטען.

רוב התוכן שיאכלס את הרכיבים האלה מוגדר, מוזרק ומנוהל ב-CastVideos.js. בואו נראה איך עושים את זה.

CastVideos.js

הסקריפט הזה מנהל את כל הלוגיקה של אפליקציית האינטרנט Cast Videos. רשימת הסרטונים והמטא-נתונים המשויכים להם שמוגדרים ב-CastVideos.js נכללים באובייקט בשם mediaJSON.

יש כמה קטעים עיקריים שאחראים יחד על ניהול הסרטון והפעלה שלו באופן מקומי ומרחוק. באופן כללי, זו אפליקציית אינטרנט פשוטה למדי.

CastPlayer היא הכיתה הראשית שמנהלת את האפליקציה כולה, מגדירה את הנגן, בוחרת מדיה ומקשרת אירועים ל-PlayerHandler כדי להפעיל מדיה. CastPlayer.prototype.initializeCastPlayer היא השיטה שמגדירה את כל הפונקציונליות של העברה (cast). CastPlayer.prototype.switchPlayer מעביר את המצב בין שחקן מקומי לנגן מרוחק. CastPlayer.prototype.setupLocalPlayer ו-CastPlayer.prototype.setupRemotePlayer מאתחלים נגנים מקומיים ומרוחקים.

PlayerHandler היא הכיתה שאחראית לניהול של הפעלת המדיה. יש כמה שיטות אחרות שאחראיות על הפרטים של ניהול מדיה והפעלה.

שאלות נפוצות

5. הוספת הלחצן להפעלת Cast

תמונה של אפליקציה שתומכת ב-Cast

באפליקציה שתומכת ב-Cast, הלחצן להפעלת Cast מוצג ברכיב הווידאו. לחיצה על לחצן ההעברה מציגה רשימה של מכשירי Cast שהמשתמשים יכולים לבחור מתוכה. אם המשתמש הפעיל תוכן באופן מקומי במכשיר השולח, הבחירה במכשיר Cast תתחיל או תמשיך את ההפעלה במכשיר ה-Cast הזה. בכל שלב במהלך הפעלת Cast, המשתמש יכול ללחוץ על הלחצן להפעלת Cast ולהפסיק את ההעברה של האפליקציה למכשיר Cast. המשתמש צריך להיות מסוגל להתחבר למכשיר ההעברה או להתנתק ממנו מכל מסך באפליקציה, כפי שמתואר ברשימת המשימות לעיצוב של Google Cast.

תצורה

בפרויקט ההתחלה נדרשים אותם יחסי תלות והגדרה כמו אלה של האפליקציה לדוגמה שהושלמה, אבל הפעם התוכן של app-start מתארח.

בדפדפן, נכנסים לכתובת ה-URL https של הדוגמה שאירחתם.

חשוב לזכור: כשמבצעים שינויים, צריך לארח מחדש את הדוגמה בשרת, בהתאם לשירות.

אתחול

למסגרת Cast יש אובייקט יחיד (singleton) גלובלי, CastContext, שמרכז את כל הפעילויות של המסגרת. צריך לאתחל את האובייקט הזה בשלב מוקדם במחזור החיים של האפליקציה. בדרך כלל הוא נקרא מתוך פונקציית קריאה חוזרת (callback) שהוקצתה ל-window['__onGCastApiAvailable'], שנקראת אחרי טעינת ה-Cast SDK והוא זמין לשימוש. במקרה כזה, CastContext נקרא ב-CastPlayer.prototype.initializeCastPlayer, שנקרא מהקריאה החוזרת (callback) שצוינה למעלה.

צריך לספק אובייקט JSON מסוג options בזמן האיפוס של CastContext. הכיתה הזו מכילה אפשרויות שמשפיעות על ההתנהגות של המסגרת. המזהה החשוב ביותר הוא מזהה אפליקציית המכשיר המקבל. המזהה הזה משמש לסינון רשימת מכשירי ה-Cast הזמינים, כך שיוצגו רק מכשירים שיכולים להריץ את האפליקציה שצוינה, ולהפעלת אפליקציית המכשיר המקבל כשמתחילים סשן העברה.

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

מוסיפים את הקוד הבא לקובץ index.html בסוף הקטע body:

<script type="text/javascript" src="https://www.gstatic.com/cv/js/sender/v1/cast_sender.js?loadCastFramework=1"></script>

מוסיפים את הקוד הבא ל-index.html כדי לאתחל את האפליקציה CastVideos, וגם כדי לאתחל את CastContext:

<script src="CastVideos.js"></script>
<script type="text/javascript">
var castPlayer = new CastPlayer();
window['__onGCastApiAvailable'] = function(isAvailable) {
  if (isAvailable) {
    castPlayer.initializeCastPlayer();
  }
};
</script>

עכשיו צריך להוסיף שיטה חדשה ב-CastVideos.js שתתאים לשיטה שרק התקשרנו אליה ב-index.html. נוסיף שיטה חדשה, בשם initializeCastPlayer, שמגדירה אפשרויות ב-CastContext ומאתחלת RemotePlayer ו-RemotePlayerControllers חדשים:

/**
 * This method sets up the CastContext, and a few other members
 * that are necessary to play and control videos on a Cast
 * device.
 */
CastPlayer.prototype.initializeCastPlayer = function() {

    var options = {};

    // Set the receiver application ID to your own (created in
    // the Google Cast Developer Console), or optionally
    // use the chrome.cast.media.DEFAULT_MEDIA_RECEIVER_APP_ID
    options.receiverApplicationId = 'C0868879';

    // Auto join policy can be one of the following three:
    // ORIGIN_SCOPED - Auto connect from same appId and page origin
    // TAB_AND_ORIGIN_SCOPED - Auto connect from same appId, page origin, and tab
    // PAGE_SCOPED - No auto connect
    options.autoJoinPolicy = chrome.cast.AutoJoinPolicy.ORIGIN_SCOPED;

    cast.framework.CastContext.getInstance().setOptions(options);

    this.remotePlayer = new cast.framework.RemotePlayer();
    this.remotePlayerController = new cast.framework.RemotePlayerController(this.remotePlayer);
    this.remotePlayerController.addEventListener(
        cast.framework.RemotePlayerEventType.IS_CONNECTED_CHANGED,
        this.switchPlayer.bind(this)
    );
};

לבסוף, צריך ליצור את המשתנים של RemotePlayer ו-RemotePlayerController:

var CastPlayer = function() {
  //...
  /* Cast player variables */
  /** @type {cast.framework.RemotePlayer} */
  this.remotePlayer = null;
  /** @type {cast.framework.RemotePlayerController} */
  this.remotePlayerController = null;
  //...
};

לחצן הפעלת Cast

עכשיו, לאחר אתחול של CastContext, צריך להוסיף את לחצן הפעלת Cast כדי לאפשר למשתמש לבחור מכשיר Cast. ה-SDK של Cast מספק רכיב של לחצן להפעלת Cast בשם google-cast-launcher עם המזהה 'castbutton"'. אפשר להוסיף אותו לרכיב הווידאו של האפליקציה על ידי הוספה של button בקטע media_control.

כך ייראה רכיב הלחצן:

<google-cast-launcher id="castbutton"></google-cast-launcher>

מוסיפים את הקוד הבא ל-index.html בקטע media_control:

<div id="media_control">
  <div id="play"></div>
  <div id="pause"></div>
  <div id="progress_bg"></div>
  <div id="progress"></div>
  <div id="progress_indicator"></div>
  <div id="fullscreen_expand"></div>
  <div id="fullscreen_collapse"></div>
  <google-cast-launcher id="castbutton"></google-cast-launcher>
  <div id="audio_bg"></div>
  <div id="audio_bg_track"></div>
  <div id="audio_indicator"></div>
  <div id="audio_bg_level"></div>
  <div id="audio_on"></div>
  <div id="audio_off"></div>
  <div id="duration">00:00:00</div>
</div>

עכשיו צריך לרענן את הדף בדפדפן Chrome. אמור להופיע לחצן העברה (cast) ברכיב הווידאו. כשלוחצים עליו, מוצגת רשימה של מכשירי Cast ברשת המקומית. זיהוי המכשירים מנוהל באופן אוטומטי על ידי דפדפן Chrome. בוחרים את מכשיר ה-Cast, ואפליקציית המקלט לדוגמה תיטען במכשיר ה-Cast.

עדיין לא הוספנו תמיכה בהפעלת מדיה, ולכן אי אפשר להפעיל סרטונים במכשיר ההעברה (cast) בשלב הזה. לוחצים על הלחצן להפעלת Cast כדי להפסיק את ההעברה.

6. העברה (cast) של תוכן סרטון

תמונה של אפליקציה שתומכת ב-Cast עם תפריט לבחירת מכשיר Cast

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

הפעלת Cast של מדיה

ככלל, אם רוצים להפעיל מדיה במכשיר Cast, צריכים להתקיים התנאים הבאים:

  1. יוצרים אובייקט JSON של MediaInfo מ-Cast SDK, שמתאר פריט מדיה.
  2. המשתמש מתחבר למכשיר ההעברה (cast) כדי להפעיל את אפליקציית המקלט.
  3. טוענים את האובייקט MediaInfo במקלט ומפעילים את התוכן.
  4. מעקב אחר סטטוס המדיה.
  5. שליחת פקודות הפעלה למכשיר הקולט על סמך אינטראקציות של משתמשים.

שלב 1 הוא למעשה מיפוי של אובייקט אחד לאובייקט אחר. MediaInfo הוא אובייקט ש-Cast SDK מבין, ו-mediaJSON הוא האנקפסולציה של פריט המדיה באפליקציה שלנו. אנחנו יכולים למפות בקלות mediaJSON ל-MediaInfo. כבר ביצענו את שלב 2 בקטע הקודם. קל לעשות את שלב 3 באמצעות Cast SDK.

באפליקציית הדוגמה CastPlayer כבר יש הבחנה בין הפעלה מקומית לבין הפעלה מרחוק בשיטה switchPlayer:

if (cast && cast.framework) {
  if (this.remotePlayer.isConnected) {
    //...

ב-Codelab הזה לא חשוב שתבינו בדיוק איך פועלת כל הלוגיקה של הנגן לדוגמה. עם זאת, חשוב להבין שיהיה צורך לשנות את נגן המדיה של האפליקציה כדי שיהיה מודע להפעלה המקומית ולהפעלה מרחוק.

בשלב הזה הנגן המקומי תמיד נמצא במצב ההפעלה המקומי כי הוא עדיין לא יודע על המצבים של ההעברה (cast). אנחנו צריכים לעדכן את ממשק המשתמש על סמך מעברי המצבים שמתרחשים במסגרת Cast. לדוגמה, אם נתחיל העברה (cast), נצטרך להפסיק את ההפעלה המקומית ולהשבית חלק מהפקדים. באופן דומה, אם נפסיק את ההעברה (cast) כשאנחנו נמצאים בבורר התצוגה הזה, נצטרך לעבור להפעלה מקומית. כדי לעשות זאת, אנחנו צריכים להקשיב לאירועים השונים שנוצרים על ידי מסגרת Cast.

ניהול סשנים של Cast

במסגרת Cast, סשן העברה מורכב מהשלבים הבאים: חיבור למכשיר, הפעלה (או הצטרפות לסשן קיים), חיבור לאפליקציית מקלט ואיפוס של ערוץ לניהול מדיה, אם רלוונטי. ערוץ הבקרה של המדיה הוא הדרך שבה מסגרת Cast שולחת ומקבלת מהמקלט הודעות שקשורות להפעלת המדיה.

סשן ההעברה יופעל באופן אוטומטי כשהמשתמש יבחר מכשיר מהלחצן 'העברה', וייפסק באופן אוטומטי כשהמשתמש יתנתק. גם החיבור מחדש לסשן של מקלט בגלל בעיות ברשת מנוהל באופן אוטומטי על ידי מסגרת ההעברה (cast).

סשנים של העברה (cast) מנוהלים על ידי CastSession, שאפשר לגשת אליו דרך cast.framework.CastContext.getInstance().getCurrentSession(). אפשר להשתמש בקריאות החזרה (callbacks) של EventListener כדי לעקוב אחרי אירועי סשן, כמו יצירה, השעיה, המשך וסיום.

באפליקציה הנוכחית שלנו, כל ניהול הסשן והמצב מתבצע בשבילנו בשיטה setupRemotePlayer. כדי להתחיל להגדיר את האפשרות הזו באפליקציה שלך, צריך להוסיף את הקוד הבא ל-CastVideos.js:

/**
 * Set the PlayerHandler target to use the remote player
 */
CastPlayer.prototype.setupRemotePlayer = function () {
    var castSession = cast.framework.CastContext.getInstance().getCurrentSession();

    this.playerHandler.setTarget(playerTarget);

    // Setup remote player volume right on setup
    // The remote player may have had a volume set from previous playback
    if (this.remotePlayer.isMuted) {
        this.playerHandler.mute();
    }
    var currentVolume = this.remotePlayer.volumeLevel * FULL_VOLUME_HEIGHT;
    var p = document.getElementById('audio_bg_level');
    p.style.height = currentVolume + 'px';
    p.style.marginTop = -currentVolume + 'px';

    this.hideFullscreenButton();

    this.playerHandler.play();
};

אנחנו עדיין צריכים לקשר את כל האירועים מהקריאות החוזרות (callback) ולטפל בכל האירועים שמתקבלים. זה קל מאוד, אז בואו נעשה את זה עכשיו:

/**
 * Set the PlayerHandler target to use the remote player
 */
CastPlayer.prototype.setupRemotePlayer = function () {
    var castSession = cast.framework.CastContext.getInstance().getCurrentSession();

    // Add event listeners for player changes which may occur outside sender app
    this.remotePlayerController.addEventListener(
        cast.framework.RemotePlayerEventType.IS_PAUSED_CHANGED,
        function() {
            if (this.remotePlayer.isPaused) {
                this.playerHandler.pause();
            } else {
                this.playerHandler.play();
            }
        }.bind(this)
    );

    this.remotePlayerController.addEventListener(
        cast.framework.RemotePlayerEventType.IS_MUTED_CHANGED,
        function() {
            if (this.remotePlayer.isMuted) {
                this.playerHandler.mute();
            } else {
                this.playerHandler.unMute();
            }
        }.bind(this)
    );

    this.remotePlayerController.addEventListener(
        cast.framework.RemotePlayerEventType.VOLUME_LEVEL_CHANGED,
        function() {
            var newVolume = this.remotePlayer.volumeLevel * FULL_VOLUME_HEIGHT;
            var p = document.getElementById('audio_bg_level');
            p.style.height = newVolume + 'px';
            p.style.marginTop = -newVolume + 'px';
        }.bind(this)
    );

    // This object will implement PlayerHandler callbacks with
    // remotePlayerController, and makes necessary UI updates specific
    // to remote playback
    var playerTarget = {};

    playerTarget.play = function () {
        if (this.remotePlayer.isPaused) {
            this.remotePlayerController.playOrPause();
        }

        var vi = document.getElementById('video_image');
        vi.style.display = 'block';
        var localPlayer = document.getElementById('video_element');
        localPlayer.style.display = 'none';
    }.bind(this);

    playerTarget.pause = function () {
        if (!this.remotePlayer.isPaused) {
            this.remotePlayerController.playOrPause();
        }
    }.bind(this);

    playerTarget.stop = function () {
         this.remotePlayerController.stop();
    }.bind(this);

    playerTarget.getCurrentMediaTime = function() {
        return this.remotePlayer.currentTime;
    }.bind(this);

    playerTarget.getMediaDuration = function() {
        return this.remotePlayer.duration;
    }.bind(this);

    playerTarget.updateDisplayMessage = function () {
        document.getElementById('playerstate').style.display = 'block';
        document.getElementById('playerstatebg').style.display = 'block';
        document.getElementById('video_image_overlay').style.display = 'block';
        document.getElementById('playerstate').innerHTML =
            this.mediaContents[ this.currentMediaIndex]['title'] + ' ' +
            this.playerState + ' on ' + castSession.getCastDevice().friendlyName;
    }.bind(this);

    playerTarget.setVolume = function (volumeSliderPosition) {
        // Add resistance to avoid loud volume
        var currentVolume = this.remotePlayer.volumeLevel;
        var p = document.getElementById('audio_bg_level');
        if (volumeSliderPosition < FULL_VOLUME_HEIGHT) {
            var vScale =  this.currentVolume * FULL_VOLUME_HEIGHT;
            if (volumeSliderPosition > vScale) {
                volumeSliderPosition = vScale + (pos - vScale) / 2;
            }
            p.style.height = volumeSliderPosition + 'px';
            p.style.marginTop = -volumeSliderPosition + 'px';
            currentVolume = volumeSliderPosition / FULL_VOLUME_HEIGHT;
        } else {
            currentVolume = 1;
        }
        this.remotePlayer.volumeLevel = currentVolume;
        this.remotePlayerController.setVolumeLevel();
    }.bind(this);

    playerTarget.mute = function () {
        if (!this.remotePlayer.isMuted) {
            this.remotePlayerController.muteOrUnmute();
        }
    }.bind(this);

    playerTarget.unMute = function () {
        if (this.remotePlayer.isMuted) {
            this.remotePlayerController.muteOrUnmute();
        }
    }.bind(this);

    playerTarget.isMuted = function() {
        return this.remotePlayer.isMuted;
    }.bind(this);

    playerTarget.seekTo = function (time) {
        this.remotePlayer.currentTime = time;
        this.remotePlayerController.seek();
    }.bind(this);

    this.playerHandler.setTarget(playerTarget);

    // Setup remote player volume right on setup
    // The remote player may have had a volume set from previous playback
    if (this.remotePlayer.isMuted) {
        this.playerHandler.mute();
    }
    var currentVolume = this.remotePlayer.volumeLevel * FULL_VOLUME_HEIGHT;
    var p = document.getElementById('audio_bg_level');
    p.style.height = currentVolume + 'px';
    p.style.marginTop = -currentVolume + 'px';

    this.hideFullscreenButton();

    this.playerHandler.play();
};

המדיה בטעינה

ב-Cast SDK, RemotePlayer ו-RemotePlayerController מספקים קבוצה של ממשקי API נוחים לניהול ההפעלה מרחוק של המדיה במכשיר המקלט. ב-CastSession שתומך בהפעלת מדיה, ה-SDK ייצור באופן אוטומטי מופעים של RemotePlayer ו-RemotePlayerController. כדי לגשת אליהם, יוצרים עותקים של cast.framework.RemotePlayer ו-cast.framework.RemotePlayerController, בהתאמה, כפי שמוצג קודם לכן ב-codelab.

בשלב הבא, אנחנו צריכים לטעון את הסרטון שנבחר כרגע במכשיר המקבל. לשם כך, אנחנו יוצרים אובייקט MediaInfo כדי שה-SDK יעבד ויעביר את הבקשה. כדי לעשות זאת, מוסיפים את הקוד הבא לקובץ setupRemotePlayer:

/**
 * Set the PlayerHandler target to use the remote player
 */
CastPlayer.prototype.setupRemotePlayer = function () {
    //...

    playerTarget.load = function (mediaIndex) {
        console.log('Loading...' + this.mediaContents[mediaIndex]['title']);
        var mediaInfo = new chrome.cast.media.MediaInfo(
            this.mediaContents[mediaIndex]['sources'][0], 'video/mp4');

        mediaInfo.metadata = new chrome.cast.media.GenericMediaMetadata();
        mediaInfo.metadata.metadataType = chrome.cast.media.MetadataType.GENERIC;
        mediaInfo.metadata.title = this.mediaContents[mediaIndex]['title'];
        mediaInfo.metadata.images = [
            {'url': MEDIA_SOURCE_ROOT + this.mediaContents[mediaIndex]['thumb']}];

        var request = new chrome.cast.media.LoadRequest(mediaInfo);
        castSession.loadMedia(request).then(
            this.playerHandler.loaded.bind(this.playerHandler),
            function (errorCode) {
                this.playerState = PLAYER_STATE.ERROR;
                console.log('Remote media load error: ' +
                    CastPlayer.getErrorMessage(errorCode));
            }.bind(this));
    }.bind(this);

    //...
};

עכשיו מוסיפים שיטה למעבר בין הפעלה מקומית להפעלה מרחוק:

/**
 * This is a method for switching between the local and remote
 * players. If the local player is selected, setupLocalPlayer()
 * is run. If there is a cast device connected we run
 * setupRemotePlayer().
 */
CastPlayer.prototype.switchPlayer = function() {
    this.stopProgressTimer();
    this.resetVolumeSlider();
    this.playerHandler.stop();
    this.playerState = PLAYER_STATE.IDLE;
    if (cast && cast.framework) {
        if (this.remotePlayer.isConnected) {
            this.setupRemotePlayer();
            return;
        }
    }
    this.setupLocalPlayer();
};

לבסוף, מוסיפים שיטה לטיפול בהודעות שגיאה של Cast:

/**
 * Makes human-readable message from chrome.cast.Error
 * @param {chrome.cast.Error} error
 * @return {string} error message
 */
CastPlayer.getErrorMessage = function(error) {
  switch (error.code) {
    case chrome.cast.ErrorCode.API_NOT_INITIALIZED:
      return 'The API is not initialized.' +
        (error.description ? ' :' + error.description : '');
    case chrome.cast.ErrorCode.CANCEL:
      return 'The operation was canceled by the user' +
        (error.description ? ' :' + error.description : '');
    case chrome.cast.ErrorCode.CHANNEL_ERROR:
      return 'A channel to the receiver is not available.' +
        (error.description ? ' :' + error.description : '');
    case chrome.cast.ErrorCode.EXTENSION_MISSING:
      return 'The Cast extension is not available.' +
        (error.description ? ' :' + error.description : '');
    case chrome.cast.ErrorCode.INVALID_PARAMETER:
      return 'The parameters to the operation were not valid.' +
        (error.description ? ' :' + error.description : '');
    case chrome.cast.ErrorCode.RECEIVER_UNAVAILABLE:
      return 'No receiver was compatible with the session request.' +
        (error.description ? ' :' + error.description : '');
    case chrome.cast.ErrorCode.SESSION_ERROR:
      return 'A session could not be created, or a session was invalid.' +
        (error.description ? ' :' + error.description : '');
    case chrome.cast.ErrorCode.TIMEOUT:
      return 'The operation timed out.' +
        (error.description ? ' :' + error.description : '');
  }
};

עכשיו מפעילים את האפליקציה. מתחברים למכשיר Cast ומתחילים להפעיל סרטון. הסרטון אמור להופיע במכשיר המקבל.

7. הוספת תמיכה ב-Cast Connect

ספריית Cast Connect מאפשרת לאפליקציות קיימות לשליחת תוכן לתקשר עם אפליקציות Android TV באמצעות פרוטוקול Cast. Cast Connect מבוסס על התשתית של Cast, כשאפליקציית Android TV פועלת כמקלט.

יחסי תלות

  • דפדפן Chrome מגרסה M87 ואילך

הגדרת תאימות של מקלט Android

כדי להפעיל את אפליקציית Android TV, שנקראת גם Android Receiver, צריך להגדיר את הדגל androidReceiverCompatible לערך true באובייקט CastOptions.

מוסיפים את הקוד הבא אל CastVideos.js בפונקציה initializeCastPlayer:

var options = {};
...
options.androidReceiverCompatible = true;

cast.framework.CastContext.getInstance().setOptions(options);

הגדרת פרטי הכניסה להפעלה

בצד השולח, אפשר לציין את CredentialsData כדי לייצג את מי שמצטרף לסשן. credentials הוא מחרוזת שאפשר להגדיר על ידי המשתמש, כל עוד אפליקציית ATV יכולה להבין אותה. CredentialsData מועבר לאפליקציה ל-Android TV רק בזמן ההפעלה או ההצטרפות. אם תגדירו אותו שוב בזמן החיבור, הוא לא יועבר לאפליקציית Android TV.

כדי להגדיר את פרטי הכניסה להפעלה, צריך להגדיר את CredentialsData בכל שלב אחרי הגדרת אפשרויות ההפעלה.

מוסיפים את הקוד הבא למחלקה CastVideos.js בפונקציה initializeCastPlayer:

cast.framework.CastContext.getInstance().setOptions(options);
...
let credentialsData = new chrome.cast.CredentialsData("{\"userId\": \"abc\"}");
cast.framework.CastContext.getInstance().setLaunchCredentialsData(credentialsData);
...

הגדרת פרטי כניסה בבקשת טעינה

אם יש הבדלים באופן שבו אפליקציית Web Receiver ואפליקציית Android TV מטפלות ב-credentials, יכול להיות שתצטרכו להגדיר פרטי כניסה נפרדים לכל אחת מהן. כדי לטפל בבעיה, צריך להוסיף את הקוד הבא אל CastVideos.js בקטע playerTarget.load בפונקציה setupRemotePlayer:

...
var request = new chrome.cast.media.LoadRequest(mediaInfo);
request.credentials = 'user-credentials';
request.atvCredentials = 'atv-user-credentials';
...

בהתאם לאפליקציית הנמען שאליה השולח מבצע העברה (cast), ה-SDK יטפל עכשיו באופן אוטומטי בפרטי הכניסה שבהם יש להשתמש בסשן הנוכחי.

בדיקת Cast Connect

השלבים להתקנת קובץ ה-APK של Android TV ב-Chromecast with Google TV:

  1. מוצאים את כתובת ה-IP של מכשיר Android TV. בדרך כלל, ניתן לעשות זאת דרך הגדרות > רשת ואינטרנט > (שם הרשת שאליה המכשיר מחובר). בצד שמאל יוצגו הפרטים והכתובת ה-IP של המכשיר ברשת.
  2. משתמשים בכתובת ה-IP של המכשיר כדי להתחבר אליו דרך ADB באמצעות הטרמינל:
$ adb connect <device_ip_address>:5555
  1. בחלון מסוף, עוברים לתיקייה ברמה העליונה של הדוגמאות ל-codelab שהורדתם בתחילת הקודלהב. לדוגמה:
$ cd Desktop/chrome_codelab_src
  1. כדי להתקין את קובץ ה-APK שבתיקייה הזו ב-Android TV, מריצים את:
$ adb -s <device_ip_address>:5555 install android-tv-app.apk
  1. עכשיו אמורה להופיע אפליקציה בשם העברת סרטונים בתפריט האפליקציות שלך במכשיר Android TV.
  2. מריצים את קוד השליחה המעודכן לאינטרנט ויוצרים סשן העברה (cast) עם מכשיר Android TV באמצעות סמל ההעברה (cast) או על ידי בחירה באפשרות Cast.. בתפריט הנפתח בדפדפן Chrome. פעולה זו אמורה להפעיל את האפליקציה ל-Android TV במקלט ה-Android ולאפשר לך לשלוט בהפעלה באמצעות השלט הרחוק של Android TV.

8. מזל טוב

עכשיו אתם יודעים איך להפעיל העברה (cast) באפליקציית וידאו באמצעות ווידג'טים של Cast SDK באפליקציית אינטרנט ב-Chrome.

לפרטים נוספים אפשר לעיין במדריך למפתחים של שולח אינטרנט.