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

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) (ดูรายละเอียดได้ในส่วนขนาดการฉายภาพสื่อ)

Surface

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

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

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

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

หากแอปกำหนดเป้าหมายเป็น 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 นี้เพื่อปรับขนาดทั้งจอแสดงผลเสมือนและ Surface เพื่อให้แน่ใจว่าอัตราส่วนกว้างยาวตรงกับเนื้อหาที่บันทึกไว้และไม่มีแถบสีดำด้านบนและด้านล่าง

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

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

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

หากแอปของคุณไม่ได้ลงทะเบียนการเรียกกลับ การเรียก 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();

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

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

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

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

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

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

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

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

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

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

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