שילוב של התכונה 'המשך צפייה' ב-Android TV

במדריך הזה מוסבר איך לשלב את התכונה "המשך צפייה" באפליקציה ל-Android TV באמצעות Engage SDK.

הכנה

מבצעים את ההוראות בקטע Pre-work במדריך לתחילת העבודה.

שילוב

יצירת ישויות

ב-SDK מוגדרות ישויות שונות שמייצגות כל סוג פריט. אוסף ההמשכים תומך בישויות הבאות:

  1. MovieEntity
  2. TvEpisodeEntity
  3. LiveStreamingVideoEntity
  4. VideoClipEntity

צריך לציין את מזהי ה-URI הספציפיים לפלטפורמה ואת תמונות הפוסטר של הישויות האלה.

אם עדיין לא עשיתם את זה, אתם צריכים גם ליצור מזהי URI להפעלה לכל פלטפורמה, כמו Android TV, ‏ Android או iOS. כך, כשמשתמש ממשיך לצפות בכל פלטפורמה, האפליקציה משתמשת ב-URI מטורגט להפעלה כדי להפעיל את קטע הווידאו.

// Required. Set this when you want continue watching entities to show up on
// Google TV
val playbackUriTv = PlatformSpecificUri.Builder()
    .setPlatformType(PlatformType.TYPE_ANDROID_TV)
    .setActionUri(Uri.parse("https://www.example.com/entity_uri_for_tv"))
    .build()

// Required. Set this when you want continue watching entities to show up on
// Google TV Android app, Entertainment Space, Playstore Widget
val playbackUriAndroid = PlatformSpecificUri.Builder()
    .setPlatformType(PlatformType.TYPE_ANDROID_MOBILE)
    .setActionUri(Uri.parse("https://www.example.com/entity_uri_for_android"))
    .build()

// Optional. Set this when you want continue watching entities to show up on
// Google TV iOS app
val playbackUriIos = PlatformSpecificUri.Builder()
    .setPlatformType(PlatformType.TYPE_IOS)
    .setActionUri(Uri.parse("https://www.example.com/entity_uri_for_ios"))
    .build()

val platformSpecificPlaybackUris =
    Arrays.asList(playbackUriTv, playbackUriAndroid, playbackUriIos)

תמונות של פוסטרים צריכות לכלול URI ומידות בפיקסלים (גובה ורוחב). כדי להתאים את התוכן למכשירים שונים, כדאי לספק כמה תמונות פוסטר. עם זאת, חשוב לוודא שלכל התמונות יש יחס גובה-רוחב של 16:9 וגובה מינימלי של 200 פיקסלים, כדי שהישות "המשך צפייה" תוצג בצורה נכונה, במיוחד בחבילת הבידור של Google. יכול להיות שלא יוצגו תמונות שהגובה שלהן קטן מ-200 פיקסלים.

val images = Arrays.asList(
    Image.Builder()
        .setImageUri(Uri.parse("http://www.example.com/entity_image1.png"))
        .setImageHeightInPixel(300)
        .setImageWidthInPixel(169)
        .build(),
    Image.Builder()
        .setImageUri(Uri.parse("http://www.example.com/entity_image2.png"))
        .setImageHeightInPixel(640)
        .setImageWidthInPixel(360)
        .build()
    // Consider adding other images for different form factors
)

MovieEntity

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

val movieEntity = MovieEntity.Builder()
   .setWatchNextType(WatchNextType.TYPE_CONTINUE)
   .setName("Movie name")
   .addPlatformSpecificPlaybackUris(platformSpecificPlaybackUris)
   .addPosterImages(images)
   // Timestamp in millis for sample last engagement time 12/1/2023 00:00:00
   .setLastEngagementTimeMillis(1701388800000)
   // Suppose the duration is 2 hours, it is 72000000 in milliseconds
   .setDurationMillis(72000000)
   // Suppose last playback offset is 1 hour, 36000000 in milliseconds
   .setLastPlayBackPositionTimeMillis(36000000)
   .setCallToActionText("Resume")
   .addTag("Action")
   .build()

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

val genres = Arrays.asList("Action", "Science fiction")
val rating1 = RatingSystem.Builder().setAgencyName("MPAA").setRating("PG-13").build()
val contentRatings = Arrays.asList(rating1)
val movieEntity = MovieEntity.Builder()
    ...
    .addGenres(genres)
    .addContentRatings(contentRatings)
    .build()

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

// Set the expiration time to be now plus 30 days in milliseconds
val expirationTime = DisplayTimeWindow.Builder()
    .setEndTimestampMillis(now().toMillis()+2592000000).build()
val movieEntity = MovieEntity.Builder()
    ...
    .addAvailabilityTimeWindow(expirationTime)
    .build()

TvEpisodeEntity

בדוגמה הבאה אפשר לראות איך יוצרים TvEpisodeEntity עם כל השדות הנדרשים:

val tvEpisodeEntity = TvEpisodeEntity.Builder()
    .setWatchNextType(WatchNextType.TYPE_CONTINUE)
    .setName("Episode name")
    .addPlatformSpecificPlaybackUris(platformSpecificPlaybackUris)
    .addPosterImages(images)
    // Timestamp in millis for sample last engagement time 12/1/2023 00:00:00
    .setLastEngagementTimeMillis(1701388800000)
    .setDurationMillis(72000000) // 2 hours in milliseconds
    // 45 minutes and 15 seconds in milliseconds is 2715000
    .setLastPlayBackPositionTimeMillis(2715000)
    .setEpisodeNumber("2")
    .setSeasonNumber("1")
    .setShowTitle("Title of the show")
    .setCallToActionText("Resume")
    .addTag("Comedy")
    .build()

מחרוזת מספר הפרק (למשל "2") ומחרוזת מספר העונה (למשל "1") יורחבו לפורמט המתאים לפני שיוצגו בכרטיס "המשך צפייה". שימו לב: הערכים צריכים להיות מחרוזת מספרית, ולא "e2", ‏ "episode 2", ‏ "s1" או "season 1".

אם לתוכנית טלוויזיה מסוימת יש עונה אחת, מגדירים את מספר העונה כ-1.

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

val genres = Arrays.asList("Action", "Science fiction")
val rating1 = RatingSystem.Builder().setAgencyName("MPAA").setRating("PG-13").build()
val contentRatings = Arrays.asList(rating1)
val tvEpisodeEntity = TvEpisodeEntity.Builder()
    ...
    .addGenres(genres)
    .addContentRatings(contentRatings)
    .setSeasonTitle("Season Title")
    .setShowTitle("Show Title")
    .build()

VideoClipEntity

דוגמה ליצירת VideoClipEntity עם כל שדות החובה.

‫VideoClipEntity מייצג קליפ שנוצר על ידי משתמש, כמו סרטון ב-YouTube.

val videoClipEntity = VideoClipEntity.Builder()
    .setPlayBackUri(Uri.parse("https://www.example.com/uri_for_current_platform"))
    .setWatchNextType(WatchNextType.TYPE_CONTINUE)
    .setName("Video clip name")
    .addPlatformSpecificPlaybackUris(platformSpecificPlaybackUris)
    .addPosterImages(images)
    // Timestamp in millis for sample last engagement time 12/1/2023 00:00:00
    .setLastEngagementTimeMillis(1701388800000)
    .setDurationMillis(600000) //10 minutes in milliseconds
    .setLastPlayBackPositionTimeMillis(300000) //5 minutes in milliseconds
    .setCallToActionText("Resume")
    .addTag("Vlog")
    .build()

אפשר גם להגדיר את היוצר, תמונת היוצר, זמן היצירה באלפיות השנייה או חלון הזמן של הזמינות .

LiveStreamingVideoEntity

הנה דוגמה ליצירת LiveStreamingVideoEntity עם כל השדות הנדרשים.

val liveStreamingVideoEntity = LiveStreamingVideoEntity.Builder()
    .setPlayBackUri(Uri.parse("https://www.example.com/uri_for_current_platform"))
    .setWatchNextType(WatchNextType.TYPE_CONTINUE)
    .setName("Live streaming name")
    .addPlatformSpecificPlaybackUris(platformSpecificPlaybackUris)
    .addPosterImages(images)
    // Timestamp in millis for sample last engagement time 12/1/2023 00:00:00
    .setLastEngagementTimeMillis(1701388800000)
    .setLastPlayBackPositionTimeMillis(36000000) //1 hour in milliseconds
    .addContentRating(contentRating)
    .setCallToActionText("Resume")
    .addTag("Live")
    .build()

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

למידע מפורט על מאפיינים ודרישות, ראו מאמרי העזרה של ה-API.

ציון נתונים של אוסף ההמשכים

‫AppEngagePublishClient אחראי לפרסום של אוסף ההמשכים. משתמשים ב-publishContinuationCluster method כדי לפרסם אובייקט ContinuationCluster.

חשוב להפעיל את הלקוח ולבדוק את זמינות השירות, כפי שמתואר במדריך לתחילת העבודה.

client.publishContinuationCluster(
    PublishContinuationClusterRequest
        .Builder()
        .setContinuationCluster(
            ContinuationCluster.Builder()
                .setAccountProfile(accountProfile)
                .addEntity(movieEntity1)
                .addEntity(movieEntity2)
                .addEntity(tvEpisodeEntity1)
                .addEntity(tvEpisodeEntity2)
                .setSyncAcrossDevices(true)
                .build()
        )
        .build()
)

כשהשירות מקבל את הבקשה, הפעולות הבאות מתבצעות בטרנזקציה אחת:

  • נתוני ContinuationCluster קיימים של שותף הפיתוח יוסרו.
  • הנתונים מהבקשה מנותחים ונשמרים ב-ContinuationCluster המעודכן.

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

ממשקי ה-API לפרסום הם ממשקי API לעדכון או להוספה (upsert), והם מחליפים את התוכן הקיים. אם אתם צריכים לעדכן ישות ספציפית באשכול ההמשך, תצטרכו לפרסם שוב את כל הישויות.

אפשר לספק נתונים של אוסף ההמשכים רק לחשבונות של מבוגרים. מותר לפרסם רק אם פרופיל החשבון שייך למבוגר.

סנכרון בין מכשירים

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

הערכים:

  • ‫true: הנתונים של אשכול ההמשך משותפים בין כל המכשירים של המשתמש כדי לספק חוויית צפייה חלקה. אנחנו ממליצים מאוד על האפשרות הזו כדי ליהנות מחוויית השימוש הטובה ביותר במכשירים שונים.
  • ‫false: נתוני אוסף ההמשכים מוגבלים למכשיר הנוכחי.

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

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

// Example to allow cross device syncing.
client.publishContinuationCluster(
    PublishContinuationClusterRequest
        .Builder()
        .setContinuationCluster(
            ContinuationCluster.Builder()
                .setAccountProfile(accountProfile)
                .setSyncAcrossDevices(true)
                .build()
        )
        .build()
)

כדי להפיק את המרב מהתכונה שלנו למעקב המרות בכמה מכשירים, צריך לוודא שהאפליקציה מקבלת את הסכמת המשתמשים ולהפעיל את האפשרות SyncAcrossDevices כדי true. כך התוכן יכול להסתנכרן בצורה חלקה בין מכשירים, מה שמוביל לחוויית משתמש טובה יותר ולמעורבות מוגברת. לדוגמה, שותף שהטמיע את התכונה הזו נהנה מעלייה של 40% במספר הקליקים על "המשך צפייה", כי התוכן שלו הוצג במספר מכשירים.

מחיקת הנתונים של Engage

כדי למחוק באופן ידני נתונים של משתמש מהשרת של Google TV לפני תקופת השמירה הרגילה של 60 יום, משתמשים ב-method‏ deleteClusters. אחרי קבלת הבקשה, השירות ימחק את כל נתוני Engage הקיימים בפרופיל החשבון או בחשבון כולו.

ה-enum‏ DeleteReason מגדיר את הסיבה למחיקת הנתונים. הקוד הבא מסיר את הנתונים של "המשך צפייה" אחרי התנתקות.


// If the user logs out from your media app, you must make the following call
// to remove continue watching data from the current google TV device,
// otherwise, the continue watching data will persist on the current
// google TV device until 60 days later.
client.deleteClusters(
    DeleteClustersRequest.Builder()
        .setAccountProfile(accountProfile)
        .setDeleteReason(DeleteReason.DELETE_REASON_USER_LOG_OUT)
        .setSyncAcrossDevices(true)
        .build()
)

בדיקה

אתם יכולים להשתמש באפליקציית האימות כדי לוודא שהשילוב של Engage SDK פועל בצורה תקינה.

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

  • בודקים את הפעולות האלה באפליקציה:
    • נכנסים לחשבון.
    • עוברים בין פרופילים(אם רלוונטי).
    • מפעילים סרטון ואז משהים אותו, או חוזרים לדף הבית.
    • סוגרים את האפליקציה בזמן שהסרטון פועל.
    • להסיר פריט מהשורה "המשך צפייה" (אם האפשרות הזו נתמכת).
  • אחרי כל פעולה, מוודאים שהאפליקציה הפעילה את ה-API של publishContinuationClusters ושהנתונים מוצגים בצורה נכונה באפליקציית האימות.
  • באפליקציית האימות יוצג סימן וי ירוק עם הכיתוב "הכול תקין" ליד ישויות שהוטמעו בצורה נכונה.

    צילום מסך של אישור אימות האפליקציה
    איור 1. Verification App Success
  • אפליקציית האימות תסמן ישויות בעייתיות.

    צילום מסך של שגיאה באפליקציית האימות
    איור 2. שגיאה באפליקציית האימות
  • כדי לפתור בעיות בישויות עם שגיאות, משתמשים בשלט הטלוויזיה כדי לבחור את הישות באפליקציית האימות וללחוץ עליה. הבעיות הספציפיות יוצגו ויופיעו בצבע אדום (ראו דוגמה בהמשך).

    פרטי השגיאה באפליקציית האימות
    איור 3. פרטי השגיאה באפליקציית האימות