Engage SDK สำหรับการแนะนำวิดีโอ

คู่มือนี้มีวิธีการสำหรับนักพัฒนาแอปในการผสานรวมเนื้อหาวิดีโอที่แนะนำโดยใช้ Engage SDK เพื่อแสดงประสบการณ์การแนะนำในแพลตฟอร์มต่างๆ ของ Google เช่น ทีวี อุปกรณ์เคลื่อนที่ และแท็บเล็ต

คำแนะนำจะใช้คลัสเตอร์คำแนะนำเพื่อแสดงภาพยนตร์และรายการทีวีจากแอปหลายแอปในการจัดกลุ่ม UI เดียว พาร์ทเนอร์นักพัฒนาแอปแต่ละรายสามารถ ออกอากาศเอนทิตีได้สูงสุด25 รายการในคลัสเตอร์คำแนะนำแต่ละรายการ และมีคลัสเตอร์คำแนะนำได้สูงสุด7 รายการต่อคำขอ

สิ่งที่ต้องเตรียมก่อนดำเนินการ

ก่อนเริ่มต้น ให้ทำตามขั้นตอนต่อไปนี้ 1. ตรวจสอบว่าแอปกำหนดเป้าหมายเป็น API ระดับ 19 ขึ้นไปสำหรับการผสานรวมนี้

  1. เพิ่มไลบรารี com.google.android.engage ลงในแอป

    มี SDK แยกต่างหากให้ใช้ในการผสานรวม โดย SDK หนึ่งสำหรับแอปบนมือถือ และอีก SDK หนึ่งสำหรับแอป TV

    สำหรับอุปกรณ์เคลื่อนที่

    
      dependencies {
        implementation 'com.google.android.engage:engage-core:1.5.9
      }
    

    สำหรับทีวี

    
      dependencies {
        implementation 'com.google.android.engage:engage-tv:1.0.5
      }
    
  2. ตั้งค่าสภาพแวดล้อมของบริการ Engage เป็นการใช้งานจริงในไฟล์ AndroidManifest.xml

    สำหรับ APK ของแอปบนอุปกรณ์เคลื่อนที่

    
    <meta-data
          android:name="com.google.android.engage.service.ENV"
          android:value="PRODUCTION">
    </meta-data>
    

    สำหรับ APK ของทีวี

    
    <meta-data
        android:name="com.google.android.engage.service.ENV"
        android:value="PRODUCTION">
    </meta-data>
    
  3. ดำเนินการเผยแพร่ในบริการที่ทำงานอยู่เบื้องหน้า

  4. เผยแพร่ข้อมูลสินค้าแนะนำอย่างน้อยวันละครั้ง โดยทริกเกอร์จาก อย่างใดอย่างหนึ่งต่อไปนี้

    1. การเข้าสู่ระบบครั้งแรกของผู้ใช้ในวันนั้น (หรือ)
    2. เมื่อผู้ใช้เริ่มโต้ตอบกับแอปพลิเคชัน

การรวมระบบ

AppEngagePublishClientเผยแพร่คลัสเตอร์คำแนะนำ ใช้เมธอด publishRecommendationClusters เพื่อเผยแพร่ออบเจ็กต์ คำแนะนำ

ใช้ isServiceAvailable()2 เพื่อตรวจสอบว่าบริการพร้อมสำหรับการ ผสานรวมหรือไม่

val client = AppEngagePublishClient(context)

client.isServiceAvailable().addOnCompleteListener { task ->
  if (task.isSuccessful) {
  // Handle IPC call success
    if(task.result) {
      // Service is available on the device, proceed with content publish
      // calls.
      client.publishRecommendationClusters(recommendationRequest)
    } else {
      // Service is not available
    }
  } else {
    // The IPC call itself fails, proceed with error handling logic here,
    // such as retry.
  }
}

คลัสเตอร์คำแนะนำและคำขอเผยแพร่

คลัสเตอร์คือการจัดกลุ่มเชิงตรรกะของเอนทิตี ตัวอย่างโค้ดต่อไปนี้ อธิบายวิธีสร้างคลัสเตอร์ตามค่ากำหนดของคุณและวิธีสร้าง คำขอเผยแพร่สำหรับคลัสเตอร์ทั้งหมด

// cluster for popular movies
val recommendationCluster1 = RecommendationCluster
  .Builder()
  .addEntity(movie1)
  .addEntity(movie2)
  .addEntity(movie3)
  .addEntity(movie4)
  .addEntity(tvShow)
  // This cluster is meant to be used as an individual provider row
  .setRecommendationClusterType(TYPE_PROVIDER_ROW)
  .setTitle("Popular Movies")
  .build()

// cluster for live TV programs
val recommendationCluster2 = RecommendationCluster
  .Builder()
  .addEntity(liveTvProgramEntity1)
  .addEntity(liveTvProgramEntity2)
  .addEntity(liveTvProgramEntity3)
  .addEntity(liveTvProgramEntity4)
  .addEntity(liveTvProgramEntity5)
 // This cluster is meant to be used as an individual provider row
  .setRecommendationClusterType(TYPE_PROVIDER_ROW)
  .setTitle("Popular Live TV Programs")
  .build()

// creating a publishing request
val recommendationRequest = PublishRecommendationClustersRequest
  .Builder()
  .setSyncAcrossDevices(true)
  .setAccountProfile(accountProfile)
  .addRecommendationCluster(recommendationCluster1)
  .addRecommendationCluster(recommendationCluster2)
  .build()

สร้างโปรไฟล์บัญชี

หากต้องการอนุญาตให้ใช้ประสบการณ์การใช้งานที่ปรับตามโปรไฟล์ของผู้ใช้บน Google TV โปรดระบุข้อมูลบัญชีและโปรไฟล์ ใช้AccountProfile เพื่อระบุข้อมูลต่อไปนี้

  1. รหัสบัญชี: ตัวระบุที่ไม่ซ้ำกันซึ่งแสดงถึงบัญชีของผู้ใช้ภายในแอปพลิเคชันของคุณ ซึ่งอาจเป็นรหัสบัญชีจริงหรือเวอร์ชันที่ปรับให้ยากต่อการอ่าน (Obfuscate) อย่างเหมาะสมก็ได้
  2. รหัสโปรไฟล์ (ไม่บังคับ): หากแอปพลิเคชันรองรับหลายโปรไฟล์ภายในบัญชีเดียว ให้ระบุตัวระบุที่ไม่ซ้ำกันสำหรับโปรไฟล์ผู้ใช้ที่เฉพาะเจาะจง
  3. Locale(ไม่บังคับ): คุณระบุภาษาที่ผู้ใช้ต้องการได้ ฟิลด์นี้มีประโยชน์หากคุณส่ง MediaActionFeedEntity ใน RecommendationRequest
// If app only supports account
val accountProfile = AccountProfile.Builder()
  .setAccountId("account_id")
  .build();

// If app supports both account and profile
val accountProfile = AccountProfile.Builder()
  .setAccountId("account_id")
  .setProfileId("profile_id")
  .build();

// set Locale
val accountProfile = AccountProfile.Builder()
  .setAccountId("account_id")
  .setProfileId("profile_id")
  .setLocale("en-US")
  .build();

เมื่อบริการได้รับคำขอ ระบบจะดำเนินการต่อไปนี้ภายในธุรกรรมเดียว

  • ระบบจะนำข้อมูล RecommendationsCluster ที่มีอยู่จากพาร์ทเนอร์นักพัฒนาออก
  • ระบบจะแยกวิเคราะห์และจัดเก็บข้อมูลจากคำขอใน RecommendationsCluster ที่อัปเดต ในกรณีที่เกิดข้อผิดพลาด ระบบจะปฏิเสธคำขอทั้งหมดและคงสถานะเดิมไว้

การซิงค์หลายอุปกรณ์

SyncAcrossDevices Flag ควบคุมว่าระบบจะแชร์ข้อมูลคลัสเตอร์คำแนะนำของผู้ใช้กับ Google TV และพร้อมใช้งานในอุปกรณ์ต่างๆ เช่น ทีวี โทรศัพท์ แท็บเล็ต หรือไม่ ต้องตั้งค่าเป็น "จริง" เพื่อให้คำแนะนำทำงานได้

แอปพลิเคชันสื่อต้องมีการตั้งค่าที่ชัดเจนเพื่อเปิดหรือปิดใช้การซิงค์ข้ามอุปกรณ์ อธิบายสิทธิประโยชน์ให้ผู้ใช้ทราบและจัดเก็บค่ากำหนดของผู้ใช้เพียงครั้งเดียว แล้วนำไปใช้ในpublishRecommendationsคำขอตามนั้น หากต้องการใช้ฟีเจอร์ข้ามอุปกรณ์ให้เกิดประโยชน์สูงสุด โปรดยืนยันว่าแอปได้รับความยินยอมจากผู้ใช้และเปิดใช้ SyncAcrossDevices เป็น true

ลบข้อมูลการค้นพบวิดีโอ

หากต้องการลบข้อมูลของผู้ใช้จากเซิร์ฟเวอร์ Google TV ด้วยตนเองก่อนระยะเวลาเก็บรักษามาตรฐาน 60 วัน ให้ใช้เมธอด client.deleteClusters() เมื่อได้รับคำขอ บริการจะลบข้อมูลการค้นพบวิดีโอที่มีอยู่ทั้งหมดสำหรับโปรไฟล์บัญชีหรือทั้งบัญชี

DeleteReason Enum จะกำหนดเหตุผลในการลบข้อมูล โค้ดต่อไปนี้จะนำคำแนะนำออกเมื่อออกจากระบบ

// If the user logs out from your media app, you must make the following call
// to remove recommendations data from the current google TV device,
// otherwise, the recommendations data persists on the current
// google TV device until 60 days later.
client.deleteClusters(
  new DeleteClustersRequest.Builder()
    .setAccountProfile(AccountProfile())
    .setReason(DeleteReason.DELETE_REASON_USER_LOG_OUT)
    .build()
)

// If the user revokes the consent to share data with Google TV,
// you must make the following call to remove recommendations data from
// all current google TV devices. Otherwise, the recommendations data persists
// until 60 days later.
client.deleteClusters(
  new DeleteClustersRequest.Builder()
    .setAccountProfile(AccountProfile())
    .setReason(DeleteReason.DELETE_REASON_LOSS_OF_CONSENT)
    .build()
)

สร้างเอนทิตี

SDK ได้กำหนดเอนทิตีต่างๆ เพื่อแสดงรายการแต่ละประเภท ระบบรองรับเอนทิตีต่อไปนี้สำหรับคลัสเตอร์คำแนะนำ

  1. MediaActionFeedEntity
  2. MovieEntity
  3. TvShowEntity
  4. LiveTvChannelEntity
  5. LiveTvProgramEntity

คำอธิบาย

ระบุคำอธิบายสั้นๆ สำหรับแต่ละเอนทิตี คำอธิบายนี้จะแสดงเมื่อผู้ใช้วางเมาส์เหนือเอนทิตี เพื่อให้ผู้ใช้ทราบรายละเอียดเพิ่มเติม

URI การเล่นเฉพาะแพลตฟอร์ม

สร้าง URI การเล่นสำหรับแต่ละแพลตฟอร์มที่รองรับ ได้แก่ Android TV, Android หรือ iOS ซึ่งจะช่วยให้ระบบเลือก URI ที่เหมาะสมสำหรับการเล่นวิดีโอบนแพลตฟอร์มที่เกี่ยวข้องได้

ในกรณีที่หายากซึ่งมี URI การเล่นเหมือนกันสำหรับทุกแพลตฟอร์ม ให้ทำซ้ำสำหรับทุกแพลตฟอร์ม

// Required. Set this when you want recommended 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()

// Optional. Set this when you want recommended entities to show up on
// Google TV Android app
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 recommended 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)

// Provide appropriate rating for the system.
val contentRating = new RatingSystem
  .Builder()
  .setAgencyName("MPAA")
  .setRating("PG-13")
  .build()

ภาพโปสเตอร์

ภาพโปสเตอร์ต้องมี URI และขนาดพิกเซล (ความสูงและความกว้าง) โปรดกำหนดเป้าหมายรูปแบบของอุปกรณ์หลายๆ แบบ โดยจัดให้มีภาพโปสเตอร์หลายภาพ และตรวจสอบว่าภาพทั้งหมดมีสัดส่วนการแสดงผล 16:9 และมีความสูงอย่างน้อย 200 พิกเซล เพื่อให้แสดงผลเอนทิตี "รายการแนะนำ" ได้อย่างถูกต้องโดยเฉพาะภายใน Entertainment Space ของ Google ทั้งนี้ระบบอาจไม่แสดงรูปภาพที่มีความสูงน้อยกว่า 200 พิกเซล

Image image1 = new Image.Builder()
  .setImageUri(Uri.parse("http://www.example.com/entity_image1.png");)
  .setImageHeightInPixel(300)
  .setImageWidthInPixel(169)
  .build()

Image image2 = new Image.Builder()
  .setImageUri(Uri.parse("http://www.example.com/entity_image2.png");)
  .setImageHeightInPixel(640)
  .setImageWidthInPixel(360)
  .build()

// And other images for different form factors.
val images = Arrays.asList(image1, image2)

เหตุผลของคำแนะนำ

คุณอาจระบุเหตุผลในการแนะนำซึ่ง Google TV สามารถใช้เพื่อสร้างเหตุผลในการแนะนำภาพยนตร์หรือรายการทีวีที่เฉพาะเจาะจงแก่ ผู้ใช้

//Allows us to construct reason: "Because it is top 10 on your Channel"
val topOnPartner = RecommendationReasonTopOnPartner
  .Builder()
  .setNum(10) //any valid integer value
  .build()

//Allows us to construct reason: "Because it is popular on your Channel"
val popularOnPartner = RecommendationReasonPopularOnPartner
  .Builder()
  .build()

//Allows us to construct reason: "New to your channel, or Just added"
val newOnPartner = RecommendationReasonNewOnPartner
  .Builder()
  .build()

//Allows us to construct reason: "Because you watched Star Wars"
val watchedSimilarTitles = RecommendationReasonWatchedSimilarTitles
  .addSimilarWatchedTitleName("Movie or TV Show Title")
  .addSimilarWatchedTitleName("Movie or TV Show Title")
  .Builder()
  .build()

//Allows us to construct reason: "Recommended for you by ChannelName"
val recommendedForUser = RecommendationReasonRecommendedForUser
  .Builder()
  .build()

val watchAgain = RecommendationReasonWatchAgain
  .Builder()
  .build()

val fromUserWatchList = RecommendationReasonFromUserWatchlist
  .Builder()
  .build()

val userLikedOnPartner = RecommendationReasonUserLikedOnPartner
  .Builder()
  .setTitleName("Movie or TV Show Title")
  .build()

val generic = RecommendationReasonGeneric.Builder().build()

กรอบเวลาที่แสดง

หากเอนทิตีควรพร้อมใช้งานในช่วงระยะเวลาจำกัดเท่านั้น ให้ตั้งค่าเวลาหมดอายุที่กำหนดเอง หากไม่มีเวลาหมดอายุที่ชัดเจน ระบบจะ ลบเอนทิตีโดยอัตโนมัติหลังจากผ่านไป 60 วัน ดังนั้นให้ตั้งเวลาหมดอายุเฉพาะในกรณีที่ต้องให้เอนทิตีหมดอายุก่อน ระบุช่วงเวลาที่พร้อมให้บริการดังกล่าวหลายช่วง

val window1 = DisplayTimeWindow
  .Builder()
  .setStartTimeStampMillis(now()+ 1.days.toMillis())
  .setEndTimeStampMillis(now()+ 30.days.toMillis())

val window2 = DisplayTimeWindow
  .Builder()
  .setEndTimeStampMillis(now()+ 30.days.toMillis())

val availabilityTimeWindows: List<DisplayTimeWindow> = listof(window1,window2)

DataFeedElementId

หากคุณได้ผสานรวมแคตตาล็อกสื่อหรือฟีดการดำเนินการสื่อกับ Google TV แล้ว คุณไม่จำเป็นต้องสร้างเอนทิตีแยกต่างหากสำหรับภาพยนตร์หรือรายการทีวี แต่สามารถ สร้างเอนทิตี MediaActionFeed ซึ่งมี ฟิลด์ DataFeedElementId ที่จำเป็นแทนได้ รหัสนี้ต้องไม่ซ้ำกันและต้องตรงกับรหัสในฟีดการดำเนินการกับสื่อ เนื่องจากจะช่วยระบุเนื้อหาฟีดที่ส่งผ่านข้อมูลแล้วและ ทำการค้นหาเนื้อหาสื่อ

val id = "dataFeedEleemntId"

MovieEntity

ต่อไปนี้เป็นตัวอย่างการสร้าง MovieEntity โดยมีฟิลด์ที่ต้องระบุทั้งหมด


val movieEntity = MovieEntity.Builder()
  .setName("Movie name")
  .setDescription("A sentence describing movie.")
  .addPlatformSpecificPlaybackUri(platformSpecificPlaybackUris)
  .addPosterImages(images)
  // Suppose the duration is 2 hours, it is 72000000 in milliseconds
  .setDurationMills(72000000)
  .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);
//Suppose release date is 11-02-2025
val releaseDate  = 1739233800000L
val movieEntity = MovieEntity.Builder()
  ...
  .addGenres(genres)
  .setReleaseDateEpochMillis(releaseDate)
  .addContentRatings(contentRatings)
  .setRecommendationReason(topOnPartner or watchedSimilarTitles)
  .addAllAvailabilityTimeWindows(availabilityTimeWindows)
  .build()

TvShowEntity

ต่อไปนี้เป็นตัวอย่างการสร้าง TvShowEntity โดยมีฟิลด์ที่ต้องระบุทั้งหมด

val tvShowEntity = TvShowEntity.Builder()
  .setName("Show title")
  .setDescription("A sentence describing TV Show.")
  .addPlatformSpecificPlaybackUri(platformSpecificPlaybackUris)
  .addPosterImages(images)
  .build();

คุณอาจระบุข้อมูลเพิ่มเติม เช่น ประเภท การจัดประเภทเนื้อหา เหตุผลในการแนะนำ ราคาของข้อเสนอ จำนวนซีซัน หรือกรอบเวลาที่พร้อมให้บริการ ซึ่ง Google TV อาจใช้เพื่อปรับปรุงการแสดงผลหรือการกรอง

val genres = Arrays.asList("Action", "Science fiction");
val rating1 = RatingSystem.Builder()
  .setAgencyName("MPAA")
  .setRating("pg-13")
  .build();
val price = Price.Builder()
  .setCurrentPrice("$14.99")
  .setStrikethroughPrice("$16.99")
  .build();
val contentRatings = Arrays.asList(rating1);
val seasonCount = 5;
val tvShowEntity = TvShowEntity.Builder()
  ...
  .addGenres(genres)
  .addContentRatings(contentRatings)
  .setRecommendationReason(topOnPartner or watchedSimilarTitles)
  .addAllAvailabilityTimeWindows(availabilityTimeWindows)
  .setSeasonCount(seasonCount)
  .setPrice(price)
  .build()

MediaActionFeedEntity

ต่อไปนี้เป็นตัวอย่างการสร้าง MediaActionFeedEntity โดยมีฟิลด์ที่ต้องระบุทั้งหมด


val mediaActionFeedEntity = MediaActionFeedEntity.Builder()
  .setDataFeedElementId(id)
  .build()

ระบุข้อมูลเพิ่มเติม เช่น คำอธิบาย เหตุผลในการแนะนำ และกรอบเวลาที่แสดง (ไม่บังคับ) ซึ่ง Google TV อาจใช้เพื่อปรับปรุงการแสดงผลหรือ วัตถุประสงค์ในการกรอง

val mediaActionFeedEntity = MediaActionFeedEntity.Builder()
  .setName("Movie name or TV Show name")
  .setDescription("A sentence describing an entity")
  .setRecommendationReason(topOnPartner or watchedSimilarTitles)
  .addPosterImages(images)
  .build()

LiveTvChannelEntity

ซึ่งแสดงถึงช่องทีวีถ่ายทอดสด ต่อไปนี้เป็นตัวอย่างการสร้าง LiveTvChannelEntity โดยมีฟิลด์ที่ต้องระบุทั้งหมด

val liveTvChannelEntity = LiveTvChannelEntity.Builder()
  .setName("Channel Name")
  // ID of the live TV channel
  .setEntityId("https://www.example.com/channel/12345")
  .setDescription("A sentence describing this live TV channel.")
  // channel playback uri must contain at least PlatformType.TYPE_ANDROID_TV
  .addPlatformSpecificPlaybackUri(channelPlaybackUris)
  .addLogoImage(logoImage)
  .build()

ระบุข้อมูลเพิ่มเติม เช่น การจัดประเภทเนื้อหาหรือเหตุผลในการแนะนำ (ไม่บังคับ)

val rating1 = RatingSystem.Builder()
  .setAgencyName("MPAA")
  .setRating("pg-13")
  .build()
val contentRatings = Arrays.asList(rating1)

val liveTvChannelEntity = LiveTvChannelEntity.Builder()
  ...
  .addContentRatings(contentRatings)
  .setRecommendationReason(topOnPartner)
  .build()

LiveTvProgramEntity

ซึ่งแสดงการ์ดโปรแกรมทีวีสดที่กำลังออกอากาศหรือกำหนดเวลาออกอากาศใน ช่องทีวีสด ต่อไปนี้เป็นตัวอย่างการสร้าง LiveTvProgramEntity โดยมีฟิลด์ที่ต้องระบุทั้งหมด

val liveTvProgramEntity = LiveTvProgramEntity.Builder()
  // First set the channel information
  .setChannelName("Channel Name")
  .setChanelId("https://www.example.com/channel/12345")
  // channel playback uri must contain at least PlatformType.TYPE_ANDROID_TV
  .addPlatformSpecificPlaybackUri(channelPlaybackUris)
  .setChannelLogoImage(channelLogoImage)
  // Then set the program or card specific information.
  .setName("Program Name")
  .setEntityId("https://www.example.com/schedule/123")
  .setDescription("Program Desccription")
  .addAvailabilityTimeWindow(
      DisplayTimeWindow.Builder()
        .setStartTimestampMillis(1756713600000L)// 2025-09-01T07:30:00+0000
        .setEndTimestampMillis(1756715400000L))// 2025-09-01T08:00:00+0000
  .addPosterImage(programImage)
  .build()

ระบุข้อมูลเพิ่มเติม เช่น การจัดประเภทเนื้อหา ประเภท หรือ เหตุผลในการแนะนำ (ไม่บังคับ)

val rating1 = RatingSystem.Builder()
  .setAgencyName("MPAA")
  .setRating("pg-13")
  .build()
val contentRatings = Arrays.asList(rating1)
val genres = Arrays.asList("Action", "Science fiction")

val liveTvProgramEntity = LiveTvProgramEntity.Builder()
  ...
  .addContentRatings(contentRatings)
  .addGenres(genres)
  .setRecommendationReason(topOnPartner)
  .build()

การทำตามขั้นตอนเหล่านี้จะช่วยให้นักพัฒนาแอปผสานรวมคำแนะนำเนื้อหาวิดีโอเข้ากับ Google TV ได้สำเร็จ ซึ่งจะช่วยเพิ่มการค้นพบและการมีส่วนร่วมของผู้ใช้ รวมถึงมอบประสบการณ์การรับชมที่สอดคล้องกันและปรับเปลี่ยนในแบบของคุณสำหรับผู้ใช้ในอุปกรณ์ทั้งหมด