Compose Media Player

repository·master·Indexed 19 days ago

https://github.com/kdroidfilter/composemediaplayer

A multiplatform video and audio player library for Compose Multiplatform. It provides native playback on Android, iOS, macOS, Windows, Linux, and Web (Wasm) without external runtime dependencies on desktop. The library is split into two modules: `composemediaplayer` for full video playback with subtitles and metadata support, and `composemediaplayer-audio` for lightweight, audio-only playback controls.

Tokens
11.6K
Snippets
46
Records
51
Agent score
66%

What's inside composemediaplayer

  1. Overview of Compose Media Player Video

    master

    Compose Media Player is a video player library designed for Compose Multiplatform. It provides a unified API to play video across Android, macOS, Windows, Linux, and Compose Web (Wasm).

    Key architectural details:

    • Desktop Platforms: Communicates with native backends via pure JNI (no JNA or external runtime dependencies like GStreamer Java bindings required).
    • Platform Backends:
      • Linux: GStreamer (via JNI)
      • Windows: Media Foundation (via JNI)
      • macOS & iOS: AVPlayer (via JNI)
      • Android: Media3
      • WasmJS: HTML5 Player
  2. Overview of Compose Media Player modules

    master

    Compose Media Player provides two specialized modules for Compose Multiplatform applications:

    • composemediaplayer (Video): A comprehensive video playback solution. It handles the video surface, subtitles, and metadata, utilizing platform-native backends for high performance. It is designed for full media integration within Compose UI.
    • composemediaplayer-audio (Audio): A lightweight alternative focused strictly on audio. It provides essential playback controls such as play, pause, stop, seek, and volume without the overhead of video rendering.
  3. Install Compose Media Player

    master

    Compose Media Player is split into two distinct modules depending on your requirements. Choose the module that matches your use case:

    1. Video player (composemediaplayer): Use this for full video playback including Compose UI integration, subtitles, and metadata support.
    2. Audio player (composemediaplayer-audio): Use this for lightweight, audio-only playback (play, pause, stop, seek, volume).
    // For Video playback
    dependencies {
        implementation("io.github.kdroidfilter:composemediaplayer:<version>")
    }
    
    // For Audio-only playback
    dependencies {
        implementation("io.github.kdroidfilter:composemediaplayer-audio:<version>")
    }
  4. Configure Picture-in-Picture (PiP) mode

    master

    PiP is supported on Android (8.0+) and iOS. It is a no-op on Desktop and Web.

    Implementation

    1. Check support via playerState.isPipSupported.
    2. Enable automatic PiP on backgrounding via playerState.isPipEnabled = true (Android).
    3. On Android, use the AutoPipEffect(playerState) composable and forward changes in your Activity's onPictureInPictureModeChanged method.
    // Android Activity requirement
    override fun onPictureInPictureModeChanged(isInPipMode: Boolean, newConfig: Configuration) {
        super.onPictureInPictureModeChanged(isInPipMode, newConfig)
        DefaultVideoPlayerState.onPictureInPictureModeChanged(isInPipMode)
    }
    
    // Composable usage
    AutoPipEffect(playerState)
  5. Enable Video Caching

    master

    You can enable disk-based caching for videos opened via openUri(). This is useful for scroll-based UIs like TikTok/Reels. The cache uses an LRU (Least Recently Used) eviction policy.

    Note: Caching only applies to URIs; local files and assets are not cached. The cache is shared across all player instances.

    val playerState = rememberVideoPlayerState(
        cacheConfig = CacheConfig(
            enabled = true,
            maxCacheSizeBytes = 200L * 1024L * 1024L // 200 MB
        )
    )
    
    // Clear cache
    playerState.clearCache()
  6. Configure Audio Mode

    master

    Control how the player interacts with other apps' audio. This is only effective on Android and iOS.

    // Mix with other apps' audio
    val playerState = rememberVideoPlayerState(
        audioMode = AudioMode(interruptionMode = InterruptionMode.MixWithOthers)
    )
    
    // Duck other apps' audio (lower their volume)
    val playerState = rememberVideoPlayerState(
        audioMode = AudioMode(interruptionMode = InterruptionMode.DuckOthers)
    )
  7. Use MacVideoPlayerState for macOS video playback

    master

    The MacVideoPlayerState class is the macOS-specific implementation of the VideoPlayerState interface. It manages media playback using a native AVFoundation player via a native bridge. It handles frame production, playback synchronization, and metadata extraction.

    Key features include:

    • Automatic Aspect Ratio Correction: Unlike the Windows implementation, macOS uses AVFoundation's display aspect ratio to ensure anamorphic content renders correctly.
    • Triple-Buffered Rendering: Uses Skia bitmaps with triple-buffering to prevent screen tearing during high-resolution playback.
    • Smooth Timeline: Uses a dedicated polling loop for the AVPlayer clock to ensure the playback slider moves smoothly even if frame delivery is irregular.
    val playerState: VideoPlayerState = MacVideoPlayerState()
  8. Add custom UI overlays to the video player

    master

    The VideoPlayerSurface accepts an overlay lambda. This UI is always visible, even in fullscreen mode. This is where you should implement your own play/pause buttons, progress bars, or fullscreen exit buttons. You can use playerState.isFullscreen to conditionally show different UI elements.

    VideoPlayerSurface(
        playerState = playerState,
        modifier = Modifier.fillMaxSize()
    ) {
        Box(modifier = Modifier.fillMaxSize()) {
            if (playerState.isFullscreen) {
                // Fullscreen UI (e.g., Exit button)
                IconButton(
                    onClick = { playerState.toggleFullscreen() },
                    modifier = Modifier.align(Alignment.TopEnd).padding(16.dp)
                ) {
                    Icon(Icons.Default.FullscreenExit, contentDescription = "Exit")
                }
            } else {
                // Regular UI (e.g., Play/Pause controls)
                Row(
                    modifier = Modifier.align(Alignment.BottomCenter).fillMaxWidth()
                ) {
                    IconButton(onClick = { 
                        if (playerState.isPlaying) playerState.pause() else playerState.play() 
                    }) {
                        Icon(if (playerState.isPlaying) Icons.Default.Pause else Icons.Default.PlayArrow, "")
                    }
                }
            }
        }
    }