API android.media.projection
ที่เปิดตัวใน Android 5 (API ระดับ 21) ช่วยให้คุณจับภาพเนื้อหาของจอแสดงผลอุปกรณ์เป็นสตรีมสื่อที่เล่น บันทึก หรือแคสต์ไปยังอุปกรณ์อื่นๆ เช่น ทีวี ได้
Android 14 (API ระดับ 34) เปิดตัวการแชร์หน้าจอแอป ซึ่งช่วยให้ผู้ใช้แชร์หน้าต่างแอปเดียวแทนการแชร์ทั้งหน้าจอของอุปกรณ์ได้ ไม่ว่าจะใช้โหมดการแบ่งหน้าจอแบบใดก็ตาม การแชร์หน้าจอแอปจะไม่รวมแถบสถานะ แถบนําทาง การแจ้งเตือน และองค์ประกอบ UI อื่นๆ ของระบบจากจอแสดงผลที่แชร์ แม้ว่าจะใช้การแชร์หน้าจอแอปเพื่อจับภาพแอปแบบเต็มหน้าจอก็ตาม ระบบจะแชร์เฉพาะเนื้อหาของแอปที่เลือกเท่านั้น
การแชร์หน้าจอแอปช่วยรักษาความเป็นส่วนตัวของผู้ใช้ เพิ่มประสิทธิภาพของผู้ใช้ และช่วยเพิ่มการทำงานแบบหลายงานโดยอนุญาตให้ผู้ใช้เรียกใช้แอปหลายแอป แต่จำกัดการแชร์เนื้อหาไว้เพียงแอปเดียว
การนําเสนอแบบดิสเพลย์ 3 รูปแบบ
การฉายสื่อจะจับภาพเนื้อหาของจอแสดงผลของอุปกรณ์หรือหน้าต่างแอป แล้วฉายภาพดังกล่าวไปยังจอแสดงผลเสมือนจริงซึ่งแสดงผลภาพบน 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];
ActivityResultLauncherstartMediaProjection = 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();
ชิปแถบสถานะและการหยุดอัตโนมัติ
การลักลอบใช้การฉายหน้าจอจะเปิดเผยข้อมูลส่วนตัวของผู้ใช้ เช่น ข้อมูลทางการเงิน เนื่องจากผู้ใช้ไม่ทราบว่ามีการแชร์หน้าจออุปกรณ์
Android 15 (API ระดับ 35) ขึ้นไปจะแสดงชิปแถบสถานะที่ใหญ่และโดดเด่นเพื่อแจ้งให้ผู้ใช้ทราบถึงการฉายหน้าจอที่ดำเนินอยู่ ผู้ใช้สามารถแตะชิปเพื่อหยุดไม่ให้แชร์ แคสต์ หรือบันทึกหน้าจอ นอกจากนี้ การฉายหน้าจอจะหยุดโดยอัตโนมัติเมื่อหน้าจออุปกรณ์ล็อกอยู่
ทดสอบความพร้อมใช้งานของชิปแถบสถานะการฉายสื่อโดยเริ่มการแชร์หน้าจอ แคสต์ หรือบันทึก ชิปจะปรากฏในแถบสถานะ
ทําดังนี้เพื่อให้แอปปล่อยทรัพยากรและอัปเดต UI เมื่อการฉายหน้าจอหยุดลงเนื่องจากผู้ใช้โต้ตอบกับชิปแถบสถานะหรือการเปิดใช้งานหน้าจอล็อก
สร้างอินสแตนซ์ของ
MediaProjection.Callback
ใช้เมธอด Callback
onStop()
ระบบจะเรียกใช้เมธอดนี้เมื่อการฉายภาพบนหน้าจอหยุดลง ปล่อยทรัพยากรที่แอปของคุณถือครองอยู่และอัปเดต UI ของแอปตามความจำเป็น
หากต้องการทดสอบการเรียกกลับ ให้แตะชิปแถบสถานะหรือล็อกหน้าจออุปกรณ์เพื่อหยุดการฉายภาพ ยืนยันว่ามีการเรียกใช้เมธอด onStop()
และแอปตอบสนองตามที่ตั้งใจไว้
แหล่งข้อมูลเพิ่มเติม
ดูข้อมูลเพิ่มเติมเกี่ยวกับการฉายสื่อได้ที่บันทึกวิดีโอและเสียงขณะเล่น