Android で動画のスクリーンショットを撮る

プログラムからスクリーンショットを撮影し保存したいことがあったんですが、動画があると普通の撮り方だと真っ黒になってしまい駄目だったので少し調べました。

動画の再生には ExoPlayer を使って再生しています。

github.com

今回はこの ExoPlayer が内部で表示するために使っている SurfaceView と TextureView のスクリーンショットの撮り方の説明です。

普通の View の場合

(普通の View とは、という疑問もありますが、今回はこれでだいたいの画面でこの方法が使えるため普通と言います)

普通の画面の場合は、 window や DataBinding から root view を取得して bitmap として保存するだけです。

fun takeScreenshotForNormalView(context: Context, view: View) {
    val bitmap = Bitmap.createBitmap(view.width, view.height, Bitmap.Config.ARGB_8888).apply {
        view.draw(Canvas(this))
    }
    saveScreenshot(context, bitmap)
}

private fun saveScreenshot(context: Context, bitmap: Bitmap) {
    val values = ContentValues().apply {
        val name = "${System.currentTimeMillis()}.jpeg"
        put(MediaStore.Images.Media.DISPLAY_NAME, name)
        put(MediaStore.Images.Media.MIME_TYPE, "image/jpeg")
        put(MediaStore.Images.Media.RELATIVE_PATH, "Pictures/Screenshots/")
        put(MediaStore.Images.Media.IS_PENDING, 1)
    }

    val collection = MediaStore.Images.Media.getContentUri(MediaStore.VOLUME_EXTERNAL_PRIMARY)
    context.contentResolver.insert(collection, values)?.let { imageUri ->
        context.contentResolver.openOutputStream(imageUri).use { outputStream ->
            bitmap.compress(Bitmap.CompressFormat.JPEG, 90, outputStream)
        }

        values.clear()
        values.put(MediaStore.Images.Media.IS_PENDING, 0)
        context.contentResolver.update(imageUri, values, null, null)

        Toast.makeText(context, "保存しました", Toast.LENGTH_SHORT).show()
    }
}

動画の場合

動画の場合 ExoPlayer を使っていると、動画自体を描画しているのは SurfaceView か TextureView になります。

この SurfaceView と TextureView を使っている場合、普通の方法でスクリーンショットを撮ると、真っ黒い画像が保存されてしまい駄目なので別の方法が必要になります。

SurfaceView

SurfaceView の場合は PixelCopy を使って bitmap を生成してそれを保存します。

fun takeScreenshotForSurfaceView(context: Context, view: SurfaceView) {
    val bitmap = Bitmap.createBitmap(
        view.width,
        view.height,
        Bitmap.Config.ARGB_8888
    )
    PixelCopy.request(
        view,
        bitmap,
        { result ->
            if (result == PixelCopy.SUCCESS) {
                saveScreenshot(context, bitmap)
            } else {
                Toast.makeText(context, "失敗しました", Toast.LENGTH_SHORT).show()
            }
        },
        Handler()
    )
}

TextureView

TexutureView の場合は、 getBitmap() というメソッドが定義されているので、それを使って bitmap を取得して保存するだけです。

fun takeScreenshotForTextureView(context: Context, view: TextureView) {
    saveScreenshot(context, view.bitmap)
}

動画を撮る場合の注意点

上記のように SurfaceView と TextureView を撮影するには PlayerView#getVideoSurfaceView() メソッドを使って動画を描画している View を取得する場合があります。
しかし、これをそのままスクリーンショットとして使うと、動画を描画している部分しか残せないため、画面全体としてスクリーンショットを撮りたい場合は不十分なので、別で画面全体の bitmap を生成し合成する、といった工夫が必要です。

サンプルコード

github.com