การฉายภาพสื่อ

API android.media.projection ที่เปิดตัวใน Android 5 (API ระดับ 21) ช่วยให้คุณจับภาพเนื้อหาของจอแสดงผลของอุปกรณ์เป็นสตรีมสื่อที่เล่น บันทึก หรือแคสต์ไปยังอุปกรณ์อื่นๆ เช่น ทีวี ได้

Android 14 (API ระดับ 34) เปิดตัวการแชร์หน้าจอแอป ซึ่งช่วยให้ผู้ใช้แชร์หน้าต่างแอปเดียวแทนการแชร์ทั้งหน้าจอของอุปกรณ์ได้ ไม่ว่าจะใช้โหมดการแบ่งหน้าจอแบบใดก็ตาม การแชร์หน้าจอแอปจะไม่รวมแถบสถานะ แถบนําทาง การแจ้งเตือน และองค์ประกอบ UI อื่นๆ ของระบบจากจอแสดงผลที่แชร์ แม้ว่าจะใช้การแชร์หน้าจอแอปเพื่อจับภาพแอปแบบเต็มหน้าจอก็ตาม ระบบจะแชร์เฉพาะเนื้อหาของแอปที่เลือกเท่านั้น

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

การนําเสนอแบบดิสเพลย์ 3 รูปแบบ

การฉายสื่อจะจับภาพเนื้อหาของจอแสดงผลของอุปกรณ์หรือหน้าต่างแอป แล้วฉายภาพดังกล่าวไปยังจอแสดงผลเสมือนจริงซึ่งแสดงผลภาพบน Surface

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

แอปพลิเคชันจะจัดเตรียม Surface โดยใช้ MediaRecorder, SurfaceTexture หรือ ImageReader ซึ่งจะแสดงเนื้อหาของจอภาพที่จับภาพไว้และช่วยให้คุณจัดการรูปภาพที่แสดงผลบน Surface ได้แบบเรียลไทม์ คุณสามารถบันทึกรูปภาพเป็นการบันทึกหรือแคสต์ไปยังทีวีหรืออุปกรณ์อื่นๆ ได้

การแสดงผลจริง

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

ใช้เมธอด getMediaProjection() ของบริการระบบ MediaProjectionManager เพื่อสร้างอินสแตนซ์ MediaProjection เมื่อคุณเริ่มกิจกรรมใหม่ เริ่มกิจกรรมด้วย Intent จากเมธอด createScreenCaptureIntent() เพื่อระบุการดำเนินการจับภาพหน้าจอ

Kotlin

val mediaProjectionManager = getSystemService(MediaProjectionManager::class.java)
var mediaProjection : MediaProjection
val startMediaProjection = registerForActivityResult( StartActivityForResult() ) { result -> if (result.resultCode == RESULT_OK) { mediaProjection = mediaProjectionManager .getMediaProjection(result.resultCode, result.data!!) } }
startMediaProjection.launch(mediaProjectionManager.createScreenCaptureIntent())

Java

final MediaProjectionManager mediaProjectionManager =
    getSystemService(MediaProjectionManager.class);
final MediaProjection[] mediaProjection = new MediaProjection[1];
ActivityResultLauncher startMediaProjection = registerForActivityResult( new StartActivityForResult(), result -> { if (result.getResultCode() == Activity.RESULT_OK) { mediaProjection[0] = mediaProjectionManager .getMediaProjection(result.getResultCode(), result.getData()); } } );
startMediaProjection.launch(mediaProjectionManager.createScreenCaptureIntent());

จอแสดงผลเสมือน

หัวใจสำคัญของการฉายสื่อคือจอแสดงผลเสมือนจริง ซึ่งคุณสร้างได้ด้วยการเรียกใช้ createVirtualDisplay() ในอินสแตนซ์ MediaProjection ดังนี้

Kotlin

virtualDisplay = mediaProjection.createVirtualDisplay(
                     "ScreenCapture",
                     width,
                     height,
                     screenDensity,
                     DisplayManager.VIRTUAL_DISPLAY_FLAG_AUTO_MIRROR,
                     surface,
                     null, null)

Java

virtualDisplay = mediaProjection.createVirtualDisplay(
                     "ScreenCapture",
                     width,
                     height,
                     screenDensity,
                     DisplayManager.VIRTUAL_DISPLAY_FLAG_AUTO_MIRROR,
                     surface,
                     null, null);

พารามิเตอร์ width และ height ระบุขนาดของจอแสดงผลเสมือนจริง หากต้องการรับค่าสำหรับความกว้างและความสูง ให้ใช้ WindowMetrics API ที่เปิดตัวใน Android 11 (API ระดับ 30) (โปรดดูรายละเอียดที่ส่วนขนาดการโปรเจ็กต์สื่อ)

พื้นผิว

ปรับขนาดพื้นผิวการฉายภาพสื่อเพื่อให้ได้เอาต์พุตที่มีความละเอียดเหมาะสม ทำให้พื้นผิวมีขนาดใหญ่ (ความละเอียดต่ำ) สำหรับการแคสต์หน้าจอไปยังทีวีหรือจอภาพคอมพิวเตอร์ และเล็ก (ความละเอียดสูง) สำหรับการบันทึกหน้าจอของอุปกรณ์

ตั้งแต่ Android 12L (API ระดับ 32) เป็นต้นไป เมื่อแสดงผลเนื้อหาที่จับภาพบนแพลตฟอร์ม ระบบจะปรับขนาดเนื้อหาให้เท่าๆ กันโดยคงสัดส่วนภาพไว้ เพื่อให้ทั้ง 2 มิติของเนื้อหา (ความกว้างและความสูง) เท่ากับหรือน้อยกว่ามิติข้อมูลที่เกี่ยวข้องของแพลตฟอร์ม จากนั้นระบบจะจัดวางเนื้อหาที่จับภาพไว้ตรงกลางพื้นผิว

วิธีการปรับขนาดของ Android 12L ช่วยปรับปรุงการแคสต์หน้าจอไปยังทีวีและจอแสดงผลขนาดใหญ่อื่นๆ โดยปรับขนาดรูปภาพบนแพลตฟอร์มให้ใหญ่ที่สุด ขณะเดียวกันก็คงสัดส่วนภาพที่เหมาะสม

สิทธิ์สำหรับบริการที่ทำงานอยู่เบื้องหน้า

หากแอปกำหนดเป้าหมายเป็น Android 14 ขึ้นไปไฟล์ Manifest ของแอปต้องมีประกาศสิทธิ์สำหรับบริการที่ทำงานอยู่เบื้องหน้าประเภท mediaProjection ดังนี้

<manifest ...>
    <uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
    <uses-permission android:name="android.permission.FOREGROUND_SERVICE_MEDIA_PROJECTION" />
    <application ...>
        <service
            android:name=".MyMediaProjectionService"
            android:foregroundServiceType="mediaProjection"
            android:exported="false">
        </service>
    </application>
</manifest>

เริ่มบริการการฉายภาพสื่อด้วยการโทรหา startForeground()

หากคุณไม่ระบุประเภทบริการที่ทำงานอยู่เบื้องหน้าในการเรียกใช้ ระบบจะตั้งค่าประเภทเป็นจำนวนเต็มแบบบิตของประเภทบริการที่ทำงานอยู่เบื้องหน้าซึ่งกำหนดไว้ในไฟล์ Manifest โดยค่าเริ่มต้น หากไฟล์ Manifest ไม่ได้ระบุประเภทบริการใดๆ ระบบจะแสดงMissingForegroundServiceTypeException

แอปของคุณต้องขอความยินยอมจากผู้ใช้ก่อนเซสชันการฉายสื่อแต่ละเซสชัน เซสชันคือการเรียกใช้ createVirtualDisplay() ครั้งเดียว โทเค็น MediaProjection ต้องใช้เพียงครั้งเดียวในการโทร

ใน Android 14 ขึ้นไป เมธอด createVirtualDisplay() จะแสดงข้อผิดพลาด SecurityException หากแอปของคุณดำเนินการอย่างใดอย่างหนึ่งต่อไปนี้

  • ส่งอินสแตนซ์ Intent ที่แสดงผลจาก createScreenCaptureIntent() ไปยัง getMediaProjection() มากกว่า 1 ครั้ง
  • เรียกใช้ createVirtualDisplay() มากกว่า 1 ครั้งใน MediaProjection อินสแตนซ์เดียวกัน

ขนาดการฉายภาพสื่อ

การฉายสื่อสามารถจับภาพทั้งหน้าจอของอุปกรณ์หรือหน้าต่างแอปได้ ไม่ว่าจะอยู่ในโหมดการแบ่งหน้าจอหรือไม่ก็ตาม

ขนาดเริ่มต้น

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

ใช้เมธอด WindowManager getMaximumWindowMetrics() ของแพลตฟอร์มเพื่อแสดงผลออบเจ็กต์ WindowMetrics สำหรับหน้าจออุปกรณ์ แม้ว่าแอปโฮสต์การฉายสื่อจะอยู่ในโหมดหลายหน้าต่างซึ่งครอบครองพื้นที่แสดงผลเพียงบางส่วนก็ตาม

หากต้องการใช้กับ API ระดับ 14 ด้านล่าง ให้ใช้เมธอด WindowMetricsCalculator computeMaximumWindowMetrics() จากไลบรารี WindowManager Jetpack

เรียกใช้เมธอด WindowMetrics getBounds() เพื่อรับความกว้างและความสูงของจอแสดงผลของอุปกรณ์

การเปลี่ยนแปลงขนาด

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

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

การปรับแต่ง

แอปสามารถปรับแต่งประสบการณ์ของผู้ใช้การฉายสื่อด้วย API MediaProjection.Callback ต่อไปนี้

  • onCapturedContentVisibilityChanged(): เปิดใช้แอปโฮสต์ (แอปที่เริ่มการฉายสื่อ) เพื่อแสดงหรือซ่อนเนื้อหาที่แชร์

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

  • onCapturedContentResize(): อนุญาตให้แอปโฮสต์เปลี่ยนขนาดของโปรเจ็กชันสื่อบนจอแสดงผลเสมือนและโปรเจ็กชันสื่อ Surface โดยอิงตามขนาดของพื้นที่การแสดงผลที่จับภาพ

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

การกู้คืนทรัพยากร

แอปของคุณควรลงทะเบียนการเรียกกลับ MediaProjection onStop() เพื่อรับการแจ้งเตือนเมื่อเซสชันการฉายสื่อหยุดลงและใช้งานไม่ได้ เมื่อเซสชันหยุดลง แอปของคุณควรปล่อยทรัพยากรที่เก็บไว้ เช่น จอแสดงผลเสมือนจริงและพื้นผิวการฉายภาพ เซสชันการฉายสื่อที่หยุดแล้วจะสร้างจอแสดงผลเสมือนจริงใหม่ไม่ได้อีกต่อไป แม้ว่าแอปของคุณจะไม่เคยสร้างจอแสดงผลเสมือนจริงสําหรับการฉายสื่อนั้นมาก่อนก็ตาม

ระบบจะเรียกใช้การเรียกกลับเมื่อโปรเจ็กชันสื่อสิ้นสุดลง การสิ้นสุดนี้อาจเกิดขึ้นได้จากหลายสาเหตุ เช่น

  • ผู้ใช้หยุดเซสชันโดยใช้ UI ของแอปหรือชิปสถานะการโปรเจ็กต์สื่อของอุปกรณ์
  • หน้าจอถูกล็อก
  • เซสชันการฉายภาพสื่ออื่นจะเริ่มขึ้น
  • กระบวนการของแอปถูกหยุด

หากแอปไม่ได้ลงทะเบียนการเรียกกลับ การเรียก createVirtualDisplay() จะแสดงข้อยกเว้น IllegalStateException

เลือกไม่ใช้

Android 14 ขึ้นไปจะเปิดใช้การแชร์หน้าจอแอปโดยค่าเริ่มต้น เซสชันการฉายสื่อแต่ละเซสชันจะให้ตัวเลือกแก่ผู้ใช้ในการแชร์หน้าต่างแอปหรือทั้งจอแสดงผล

แอปสามารถเลือกไม่ใช้การแชร์หน้าจอแอปได้โดยเรียกใช้เมธอด createScreenCaptureIntent(MediaProjectionConfig) พร้อมอาร์กิวเมนต์ MediaProjectionConfig ที่แสดงผลจากการเรียกใช้ createConfigForDefaultDisplay()

การเรียกใช้ createScreenCaptureIntent(MediaProjectionConfig) ที่มีอาร์กิวเมนต์ MediaProjectionConfig ซึ่งแสดงผลจากการเรียกใช้ createConfigForUserChoice() จะเหมือนกับลักษณะการทำงานเริ่มต้น กล่าวคือ การเรียกใช้ createScreenCaptureIntent()

แอปที่ปรับขนาดได้

ทำให้แอปการฉายสื่อปรับขนาดได้เสมอ (resizeableActivity="true") แอปที่ปรับขนาดได้รองรับการเปลี่ยนแปลงการกำหนดค่าอุปกรณ์และโหมดหลายหน้าต่าง (ดูการรองรับหลายหน้าต่าง)

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

Kotlin

val windowContext = context.createWindowContext(context.display!!,
      WindowManager.LayoutParams.TYPE_APPLICATION, null)
val projectionMetrics = windowContext.getSystemService(WindowManager::class.java)
      .maximumWindowMetrics

Java

Context windowContext = context.createWindowContext(context.getDisplay(),
      WindowManager.LayoutParams.TYPE_APPLICATION, null);
WindowMetrics projectionMetrics = windowContext.getSystemService(WindowManager.class)
      .getMaximumWindowMetrics();

ชิปแถบสถานะและการหยุดอัตโนมัติ

Screen projection exploits expose private user data such as financial information because users don't realize their device screen is being shared.

For apps running on devices with Android 15 QPR1 or higher, a status bar chip that is large and prominent alerts users to any in‑progress screen projection. Users can tap the chip to stop their screen from being shared, cast, or recorded. Also, screen projection automatically stops when the device screen is locked.

รูปที่ 2 ชิปแถบสถานะสำหรับการแชร์หน้าจอ การแคสต์ และการบันทึก

ทดสอบความพร้อมใช้งานของชิปแถบสถานะการฉายสื่อโดยเริ่มการแชร์หน้าจอ แคสต์ หรือบันทึก ชิปจะปรากฏในแถบสถานะ

ทําดังนี้เพื่อให้แอปปล่อยทรัพยากรและอัปเดต UI เมื่อการฉายหน้าจอหยุดลงเนื่องจากผู้ใช้โต้ตอบกับชิปแถบสถานะหรือการเปิดใช้งานหน้าจอล็อก

  • สร้างอินสแตนซ์ของ MediaProjection.Callback

  • ใช้เมธอด Callback onStop() ระบบจะเรียกใช้เมธอดนี้เมื่อการฉายภาพบนหน้าจอหยุดลง ปล่อยทรัพยากรที่แอปของคุณถือครองอยู่และอัปเดต UI ของแอปตามความจำเป็น

หากต้องการทดสอบการติดต่อกลับ ให้แตะชิปแถบสถานะหรือล็อกหน้าจออุปกรณ์เพื่อหยุดการฉายหน้าจอ ยืนยันว่ามีการเรียกใช้เมธอด onStop() และแอปตอบสนองตามที่ตั้งใจไว้

แหล่งข้อมูลเพิ่มเติม

ดูข้อมูลเพิ่มเติมเกี่ยวกับการฉายสื่อได้ที่บันทึกวิดีโอและเสียงขณะเล่น