← Back to Tutorials Chapter 13

Multimedia & Graphics

MediaPlayer

class AudioPlayer(private val context: Context) {
    private var mediaPlayer: MediaPlayer? = null

    fun playFromUrl(url: String) {
        mediaPlayer = MediaPlayer().apply {
            setDataSource(url)
            setOnPreparedListener { start() }
            setOnErrorListener { _, _, _ -> false }
            prepareAsync()
        }
    }

    fun playFromResource(resId: Int) {
        mediaPlayer = MediaPlayer.create(context, resId)
        mediaPlayer?.start()
    }

    fun pause() {
        mediaPlayer?.pause()
    }

    fun resume() {
        mediaPlayer?.start()
    }

    fun stop() {
        mediaPlayer?.stop()
        mediaPlayer?.release()
        mediaPlayer = null
    }

    fun getCurrentPosition(): Int = mediaPlayer?.currentPosition ?: 0
}

Camera API

// CameraX (modern camera API)
// build.gradle.kts
// implementation("androidx.camera:camera-camera2:1.3.0")
// implementation("androidx.camera:camera-lifecycle:1.3.0")
// implementation("androidx.camera:camera-view:1.3.0")

class CameraActivity : AppCompatActivity() {
    private lateinit var imageCapture: ImageCapture

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_camera)

        val cameraProviderFuture = ProcessCameraProvider.getInstance(this)
        cameraProviderFuture.addListener({
            val cameraProvider = cameraProviderFuture.get()
            val preview = Preview.Builder().build()
            imageCapture = ImageCapture.Builder()
                .setCaptureMode(ImageCapture.CAPTURE_MODE_MINIMIZE_LATENCY)
                .build()

            val cameraSelector = CameraSelector.DEFAULT_BACK_CAMERA
            cameraProvider.bindToLifecycle(this, cameraSelector, preview, imageCapture)
        }, ContextCompat.getMainExecutor(this))

        // Take photo
        findViewById<Button>(R.id.captureBtn).setOnClickListener {
            val photoFile = File(externalMediaDirs[0],
                "photo_${System.currentTimeMillis()}.jpg")
            val outputOptions = ImageCapture.OutputFileOptions.Builder(photoFile).build()
            imageCapture.takePicture(outputOptions, ContextCompat.getMainExecutor(this),
                object : ImageCapture.OnImageSavedCallback {
                    override fun onImageSaved(output: ImageCapture.OutputFileResults) {
                        Toast.makeText(this@CameraActivity, "Photo saved!", Toast.LENGTH_SHORT).show()
                    }
                    override fun onError(exc: ImageCaptureException) {
                        Toast.makeText(this@CameraActivity, "Error: ${exc.message}", Toast.LENGTH_SHORT).show()
                    }
                })
        }
    }
}

Custom Canvas Drawing

class CustomCanvasView(context: Context, attrs: AttributeSet?) : View(context, attrs) {
    private val paint = Paint().apply {
        isAntiAlias = true
        style = Paint.Style.FILL
    }

    override fun onDraw(canvas: Canvas) {
        super.onDraw(canvas)

        // Draw background
        canvas.drawColor(Color.WHITE)

        // Draw circle
        paint.color = Color.parseColor("#43a047")
        canvas.drawCircle(200f, 200f, 100f, paint)

        // Draw rectangle
        paint.color = Color.parseColor("#1E88E5")
        canvas.drawRect(50f, 350f, 350f, 500f, paint)

        // Draw text
        paint.color = Color.BLACK
        paint.textSize = 40f
        canvas.drawText("Hello Canvas!", 50f, 600f, paint)

        // Draw line
        paint.strokeWidth = 8f
        paint.style = Paint.Style.STROKE
        canvas.drawLine(50f, 650f, 350f, 650f, paint)

        // Draw path
        val path = Path().apply {
            moveTo(100f, 700f)
            lineTo(200f, 750f)
            lineTo(300f, 700f)
            close()
        }
        paint.style = Paint.Style.FILL
        paint.color = Color.parseColor("#FFC107")
        canvas.drawPath(path, paint)
    }
}

Image Effects with Bitmap

fun applySepiaEffect(bitmap: Bitmap): Bitmap {
    val result = bitmap.copy(Bitmap.Config.ARGB_8888, true)
    val canvas = Canvas(result)
    val paint = Paint()
    val sepiaFilter = ColorMatrix(floatArrayOf(
        0.393f, 0.769f, 0.189f, 0f, 0f,
        0.349f, 0.686f, 0.168f, 0f, 0f,
        0.272f, 0.534f, 0.131f, 0f, 0f,
        0f,     0f,     0f,     1f, 0f
    ))
    paint.colorFilter = ColorMatrixColorFilter(sepiaFilter)
    canvas.drawBitmap(bitmap, 0f, 0f, paint)
    return result
}

Animations

// View animation
view.animate()
    .translationX(200f)
    .translationY(100f)
    .alpha(0.5f)
    .scaleX(1.5f)
    .scaleY(1.5f)
    .rotation(360f)
    .setDuration(1000)
    .setInterpolator(AccelerateDecelerateInterpolator())
    .withEndAction { println("Animation done") }
    .start()

// Lottie animations (JSON-based)
// implementation("com.airbnb.android:lottie:6.3.0")

<com.airbnb.lottie.LottieAnimationView
    android:id="@+id/animationView"
    android:layout_width="200dp"
    android:layout_height="200dp"
    app:lottie_rawRes="@raw/loading_animation"
    app:lottie_loop="true"
    app:lottie_autoPlay="true" />
Exercise: Create a photo editor app. Use CameraX to capture a photo, display it in an ImageView, and provide three filters: sepia, grayscale, and invert colors using Bitmap color matrix effects. Add a Lottie loading animation while processing the image.