### Initialize Koin DI Modules Source: https://context7.com/volvo-cars/automotive-media-sample/llms.txt The DependencyBootstrapper initializes Koin for dependency injection by starting the Koin framework with the application context and listing all the necessary dependency modules for the application. This includes modules for common utilities, data access, and media playback. ```kotlin package com.volvocars.mediasample.common object DependencyBootstrapper { fun bootstrap(application: Application) { startKoin { androidContext(application) modules(dependencyModules()) } } private fun dependencyModules() = listOf( CommonDependencyModule.dependencies(), DataAccessDependencyModule.dependencies(), PlaybackDependencyModule.dependencies() ) } ``` -------------------------------- ### Implement SongRepository and PlaylistFactory in Kotlin Source: https://context7.com/volvo-cars/automotive-media-sample/llms.txt The `SongRepositoryImpl` provides a concrete implementation for fetching playlist data by delegating to a `PlaylistFactory`. The `PlaylistFactory` is responsible for creating sample playlist content, which in this example is hardcoded. This factory uses a `Context` and a `ResourceUriUtil` to resolve resource URIs for album art and audio files, demonstrating how to integrate with Android resources. ```kotlin // SongRepositoryImpl.kt - Repository implementation package com.volvocars.mediasample.dataaccess import com.volvocars.mediasample.domain.Playlist import com.volvocars.mediasample.domain.SongRepository class SongRepositoryImpl(private val playlistFactory: PlaylistFactory) : SongRepository { override fun retrievePlaylist(): Playlist = playlistFactory.createPlaylist() } // PlaylistFactory.kt - Creates sample playlist content class PlaylistFactory( private val context: Context, private val resourceUriUtil: ResourceUriUtil ) { fun createPlaylist() = Playlist( id = "volvo-pre-listen-playlist-id", songs = getSongs(), ) private fun getSongs() = listOf( Song( id = "premier_song_id", title = context.getString(R.string.sample_song_title), subtitle = context.getString(R.string.sample_song_subtitle), description = context.getString(R.string.sample_song_description), mediaArtUri = resourceUriUtil.getUriFor(R.drawable.abba_album_art).toString(), mediaUri = resourceUriUtil.getUriFor(R.raw.test_audio_track).toString(), artist = context.getString(R.string.sample_artist_name), ) ) } // Dummy classes for compilation class Context class ResourceUriUtil { fun getUriFor(resourceId: Int): android.net.Uri = android.net.Uri.parse("android.resource://com.example.app/$resourceId") } // Dummy domain models and R object data class Song( val id: String, val artist: String, val title: String, val subtitle: String, val description: String, val mediaArtUri: String, val mediaUri: String ) data class Playlist( val id: String, val songs: List, ) interface SongRepository { fun retrievePlaylist(): Playlist } object R { object string { val sample_song_title = "Sample Song Title" val sample_song_subtitle = "Sample Song Subtitle" val sample_song_description = "Sample Song Description" val sample_artist_name = "Sample Artist Name" } object drawable { val abba_album_art = 1 } object raw { val test_audio_track = 2 } } ``` -------------------------------- ### GET /media/playlist/{id} Source: https://context7.com/volvo-cars/automotive-media-sample/llms.txt Retrieves a list of media items associated with a specific playlist ID for browsing purposes. ```APIDOC ## GET /media/playlist/{id} ### Description Retrieves the list of media metadata items belonging to a specific playlist ID from the media library. ### Method GET ### Endpoint /media/playlist/{id} ### Parameters #### Path Parameters - **id** (string) - Required - The unique identifier of the playlist to retrieve. ### Response #### Success Response (200) - **items** (List) - A list of media items contained within the playlist. #### Response Example [ { "id": "song_1", "title": "Song One" }, { "id": "song_2", "title": "Song Two" } ] ``` -------------------------------- ### Implement Playback Control with PlaybackPreparer Source: https://context7.com/volvo-cars/automotive-media-sample/llms.txt The PlaybackPreparer class implements MediaSessionConnector.PlaybackPreparer to handle playback requests. It supports preparing media by ID, search query, or URI, and manages the ExoPlayer state accordingly. ```kotlin class PlaybackPreparer( private val player: Player, private val mediaLibrary: MediaLibrary ) : MediaSessionConnector.PlaybackPreparer { override fun getSupportedPrepareActions() = PlaybackStateCompat.ACTION_PREPARE_FROM_MEDIA_ID or PlaybackStateCompat.ACTION_PLAY_FROM_MEDIA_ID or PlaybackStateCompat.ACTION_PREPARE_FROM_SEARCH or PlaybackStateCompat.ACTION_PLAY_FROM_SEARCH override fun onPrepare(playWhenReady: Boolean) { player.playWhenReady = playWhenReady player.stop() player.prepare() } override fun onPrepareFromMediaId(mediaId: String, playWhenReady: Boolean, extras: Bundle?) { val playableMedia = mediaLibrary.getSongById(mediaId)?.toExoMediaItem() if (playableMedia == null) { loge("Media not found.") return } player.playWhenReady = playWhenReady player.stop() player.setMediaItem(playableMedia) player.prepare() } } ``` -------------------------------- ### POST /playback/prepare/{mediaId} Source: https://context7.com/volvo-cars/automotive-media-sample/llms.txt Prepares a specific media item for playback by ID using the PlaybackPreparer interface. ```APIDOC ## POST /playback/prepare/{mediaId} ### Description Prepares a specific media item for playback by its unique media ID. This updates the player state and loads the media item into the ExoPlayer instance. ### Method POST ### Endpoint /playback/prepare/{mediaId} ### Parameters #### Path Parameters - **mediaId** (string) - Required - The unique identifier of the song to prepare. #### Request Body - **playWhenReady** (boolean) - Required - Whether the player should start playback immediately after preparation. - **extras** (Bundle) - Optional - Additional configuration parameters. ### Response #### Success Response (200) - **status** (string) - Indicates that the media has been successfully prepared for playback. #### Response Example { "status": "success", "message": "Media prepared" } ``` -------------------------------- ### Define Media Playback Dependencies with Koin Source: https://context7.com/volvo-cars/automotive-media-sample/llms.txt This Koin module defines dependencies related to media playback, including factories for MediaItemFactory, MediaLibrary, AudioAttributes, and the Player implementation (SimpleExoPlayer). It also sets up MediaSessionCompat for media control. ```kotlin package com.volvocars.mediasample.common object PlaybackDependencyModule { fun dependencies() = module { factory { MediaItemFactory(context = androidApplication()) } single { MediaLibrary(mediaItemFactory = get(), songRepository = get()) } factory { AudioAttributes.Builder() .setContentType(C.CONTENT_TYPE_MUSIC) .setUsage(C.USAGE_MEDIA) .build() } factory { (serviceContext: Context) -> val audioAttributes = get() SimpleExoPlayer.Builder(serviceContext).build().apply { setAudioAttributes(audioAttributes, true) } } factory { (serviceContext: Context) -> MediaSessionCompat(serviceContext, "MusicService") } } } ``` -------------------------------- ### Implement MediaBrowserService in Kotlin Source: https://context7.com/volvo-cars/automotive-media-sample/llms.txt The MusicService class extends MediaBrowserServiceCompat to expose media content and playback services to the Android Automotive system. It manages MediaSession, integrates ExoPlayer, and handles client requests for media browsing. ```kotlin package com.volvocars.mediasample.mediaplayback import android.app.PendingIntent import android.os.Bundle import androidx.media.MediaBrowserServiceCompat import androidx.media.MediaBrowserCompat import androidx.media.session.MediaButtonReceiver import android.support.v4.media.session.MediaSessionCompat import com.google.android.exoplayer2.Player import com.google.android.exoplayer2.ext.mediasession.MediaSessionConnector import org.koin.android.ext.android.inject import org.koin.core.parameter.parametersOf class MusicService : MediaBrowserServiceCompat() { // Dependencies injected via Koin private val mediaLibrary: MediaLibrary by inject() private val mediaPlayer: Player by inject { parametersOf(this) } private val mediaSession: MediaSessionCompat by inject { parametersOf(this) } private lateinit var mediaSessionConnector: MediaSessionConnector override fun onCreate() { super.onCreate() // Build PendingIntent for launching UI (optional for automotive) val sessionActivityPendingIntent = packageManager?.getLaunchIntentForPackage(packageName)?.let { sessionIntent -> PendingIntent.getActivity(this, 0, sessionIntent, 0) } // Configure and activate MediaSession mediaSession.setSessionActivity(sessionActivityPendingIntent) mediaSession.isActive = true // Connect ExoPlayer to MediaSession mediaSessionConnector = MediaSessionConnector(mediaSession) mediaSessionConnector.setPlayer(mediaPlayer) mediaSessionConnector.setPlaybackPreparer(PlaybackPreparer(mediaPlayer, mediaLibrary)) sessionToken = mediaSession.sessionToken } override fun onDestroy() { mediaSession.run { isActive = false release() } mediaPlayer.release() } // Called when a client connects - return the root of your media tree override fun onGetRoot( clientPackageName: String, clientUid: Int, rootHints: Bundle? ): BrowserRoot { val extras = Bundle().apply { // Configure grid-style display for browsable items putInt( MediaConstants.DESCRIPTION_EXTRAS_KEY_CONTENT_STYLE_BROWSABLE, MediaConstants.DESCRIPTION_EXTRAS_VALUE_CONTENT_STYLE_GRID_ITEM ) putInt( MediaConstants.DESCRIPTION_EXTRAS_KEY_CONTENT_STYLE_PLAYABLE, MediaConstants.DESCRIPTION_EXTRAS_VALUE_CONTENT_STYLE_GRID_ITEM ) } return BrowserRoot(BROWSABLE_ROOT_ID, extras) } // Called to load children of a media item (playlist contents) override fun onLoadChildren( parentId: String, result: Result> ) { val mediaItems = mediaLibrary.getPlaylistById(parentId)?.map { it.toMediaItem() } result.sendResult(mediaItems) } } ``` -------------------------------- ### Define Data Access Dependencies with Koin Source: https://context7.com/volvo-cars/automotive-media-sample/llms.txt This Koin module defines dependencies for the data access layer, including a factory for PlaylistFactory and a singleton for SongRepositoryImpl. These are essential for managing and accessing media data within the application. ```kotlin package com.volvocars.mediasample.common object DataAccessDependencyModule { fun dependencies() = module { factory { PlaylistFactory(context = androidApplication(), resourceUriUtil = get()) } single { SongRepositoryImpl(playlistFactory = get()) } } } ``` -------------------------------- ### Define Domain Models for Song and Playlist in Kotlin Source: https://context7.com/volvo-cars/automotive-media-sample/llms.txt This section defines the core domain models for representing media content. The `Song` data class holds all necessary metadata for a playable track, including IDs, titles, URIs for media and artwork, and artist information. The `Playlist` data class groups multiple `Song` objects together, representing a collection or list of tracks. An interface `SongRepository` is also defined for abstracting data retrieval. ```kotlin // Song.kt - Domain model for a playable track package com.volvocars.mediasample.domain data class Song( val id: String, val artist: String, val title: String, val subtitle: String, val description: String, val mediaArtUri: String, val mediaUri: String ) // Playlist.kt - Domain model for a collection of songs data class Playlist( val id: String, val songs: List, ) // SongRepository.kt - Interface for retrieving media content interface SongRepository { fun retrievePlaylist(): Playlist } ``` -------------------------------- ### Create MediaMetadataCompat Objects with MediaItemFactory Source: https://context7.com/volvo-cars/automotive-media-sample/llms.txt The MediaItemFactory class in Kotlin is used to generate MediaMetadataCompat objects, which are essential for representing media items in Android's MediaBrowserService. It provides methods to create both browsable container items (like folders or playlists) and playable media items (like songs). It leverages Kotlin extension properties for a more concise metadata building process. ```kotlin package com.volvocars.mediasample.mediaplayback import android.content.Context import androidx.media.MediaBrowserServiceCompat import androidx.media.MediaBrowserServiceCompat.MediaItem import androidx.media.session.MediaSessionCompat import androidx.media3.common.MediaItem import androidx.media3.common.MediaMetadata // Assuming MediaMetadataCompat is available via androidx.media:media: // If not, you might need to use androidx.media3.common.MediaMetadata directly or adapt. // For this example, we'll assume MediaMetadataCompat is used as in the original context. import androidx.media.MediaMetadataCompat class MediaItemFactory(private val context: Context) { // Create a browsable root item (folder/playlist) fun createBrowsableRootMediaItem(rootId: String): MediaMetadataCompat = MediaMetadataCompat.Builder().apply { putString(MediaMetadataCompat.METADATA_KEY_MEDIA_ID, rootId) putString(MediaMetadataCompat.METADATA_KEY_DISPLAY_TITLE, context.getString(R.string.browsable_tab_label)) putLong(MediaMetadataCompat.METADATA_KEY_FLAGS, MediaBrowserServiceCompat.MediaItem.FLAG_BROWSABLE.toLong()) }.build() // Create a playable media item (song) fun createPlayableMediaItem(song: Song, playlistSize: Int, songNumber: Int): MediaMetadataCompat = MediaMetadataCompat.Builder().apply { putString(MediaMetadataCompat.METADATA_KEY_MEDIA_ID, song.id) putString(MediaMetadataCompat.METADATA_KEY_TITLE, song.title) putString(MediaMetadataCompat.METADATA_KEY_MEDIA_URI, song.mediaUri) putString(MediaMetadataCompat.METADATA_KEY_ARTIST, song.artist) putString(MediaMetadataCompat.METADATA_KEY_ALBUM_ART_URI, song.mediaArtUri) putLong(MediaMetadataCompat.METADATA_KEY_TRACK_COUNT, playlistSize.toLong()) putLong(MediaMetadataCompat.METADATA_KEY_TRACK_NUMBER, songNumber.toLong()) putLong(MediaMetadataCompat.METADATA_KEY_FLAGS, MediaBrowserServiceCompat.MediaItem.FLAG_PLAYABLE.toLong()) // Display metadata putString(MediaMetadataCompat.METADATA_KEY_DISPLAY_TITLE, song.title) putString(MediaMetadataCompat.METADATA_KEY_DISPLAY_SUBTITLE, song.subtitle) putString(MediaMetadataCompat.METADATA_KEY_DISPLAY_DESCRIPTION, song.description) putString(MediaMetadataCompat.METADATA_KEY_DISPLAY_ICON_URI, song.mediaArtUri) }.build() } // Extension to convert MediaMetadataCompat to ExoPlayer MediaItem fun MediaMetadataCompat.toExoMediaItem(): MediaItem { val mediaItemBuilder = MediaItem.Builder() this.getString(METADATA_KEY_MEDIA_URI)?.let { uriString -> mediaItemBuilder.setUri(uriString) } // You might want to transfer other metadata to MediaItem as well, e.g., MediaMetadata val mediaMetadata = MediaMetadata.Builder() .setTitle(this.getString(METADATA_KEY_TITLE)) .setArtist(this.getString(METADATA_KEY_ARTIST)) .setAlbumArtist(this.getString(METADATA_KEY_ALBUM_ARTIST)) // Assuming ALBUM_ARTIST might be set .setAlbumTitle(this.getString(METADATA_KEY_ALBUM)) .setTrackNumber(this.getLong(METADATA_KEY_TRACK_NUMBER).toInt()) .setTotalTracks(this.getLong(METADATA_KEY_TRACK_COUNT).toInt()) .setArtworkData(null, null) // Placeholder for artwork data if needed .build() mediaItemBuilder.setMediaMetadata(mediaMetadata) return mediaItemBuilder.build() } // Dummy R and Song class for compilation object R { object string { val browsable_tab_label = "Browsable Tab Label" val sample_song_title = "Sample Song Title" val sample_song_subtitle = "Sample Song Subtitle" val sample_song_description = "Sample Song Description" val sample_artist_name = "Sample Artist Name" } object drawable { val abba_album_art = 1 } object raw { val test_audio_track = 2 } } data class Song( val id: String, val artist: String, val title: String, val subtitle: String, val description: String, val mediaArtUri: String, val mediaUri: String ) // Dummy Context for compilation class Context // Dummy METADATA_KEY constants for compilation const val METADATA_KEY_MEDIA_ID = "media_id" const val METADATA_KEY_TITLE = "title" const val METADATA_KEY_MEDIA_URI = "media_uri" const val METADATA_KEY_ARTIST = "artist" const val METADATA_KEY_ALBUM_ART_URI = "album_art_uri" const val METADATA_KEY_TRACK_COUNT = "track_count" const val METADATA_KEY_TRACK_NUMBER = "track_number" const val METADATA_KEY_FLAGS = "flags" const val METADATA_KEY_DISPLAY_TITLE = "display_title" const val METADATA_KEY_DISPLAY_SUBTITLE = "display_subtitle" const val METADATA_KEY_DISPLAY_DESCRIPTION = "display_description" const val METADATA_KEY_DISPLAY_ICON_URI = "display_icon_uri" const val METADATA_KEY_ALBUM_ARTIST = "album_artist" const val METADATA_KEY_ALBUM = "album" ``` -------------------------------- ### Manage Media Library Structure with MediaLibrary Source: https://context7.com/volvo-cars/automotive-media-sample/llms.txt The MediaLibrary class organizes media into a tree structure suitable for Android Automotive media browsers. It utilizes a factory and repository to map playlists and songs to browsable and playable media items. ```kotlin class MediaLibrary(mediaItemFactory: MediaItemFactory, songRepository: SongRepository) { private val playlist: Playlist = songRepository.retrievePlaylist() private val library = mutableMapOf>() init { library[BROWSABLE_ROOT_ID] = listOf( mediaItemFactory.createBrowsableRootMediaItem(playlist.id) ) library[playlist.id] = playlist.songs.mapIndexed { songIndex, song -> mediaItemFactory.createPlayableMediaItem( song = song, playlistSize = playlist.songs.size, songNumber = songIndex + 1 ) } } fun getPlaylistById(id: String): List? { return library[id] } fun getSongById(id: String): MediaMetadataCompat? { return library[playlist.id]?.find { it.id == id } } } ``` -------------------------------- ### Implement Logging Extension Functions Source: https://context7.com/volvo-cars/automotive-media-sample/llms.txt These Kotlin extension functions provide convenient logging capabilities by automatically using the class name as the log tag. They abstract the underlying logging implementation, making it easier to log debug, info, warning, and error messages. ```kotlin package com.volvocars.mediasample.common.logging fun Any.logd(message: String) { LoggerFactory.create().debug(logTag(), message) } fun Any.logi(message: String) { LoggerFactory.create().info(logTag(), message) } fun Any.logw(message: String) { LoggerFactory.create().warning(logTag(), message) } fun Any.loge(message: String) { LoggerFactory.create().error(logTag(), message) } fun Any.loge(message: String? = null, throwable: Throwable) { LoggerFactory.create().error(logTag(), message, throwable) } fun Any.logTag(): String = javaClass.simpleName ``` -------------------------------- ### Configure AndroidManifest for Automotive Media App Source: https://context7.com/volvo-cars/automotive-media-sample/llms.txt This AndroidManifest.xml file configures the application for Android Automotive, declaring necessary features and services. It specifies the app as an audio category, includes automotive-specific features, and declares the MusicService for media browsing capabilities. ```xml ``` -------------------------------- ### Define Logger Interface Source: https://context7.com/volvo-cars/automotive-media-sample/llms.txt The Logger interface defines the contract for logging operations, including methods for debug, info, warning, and error logging. It supports logging messages with or without an associated throwable exception. ```kotlin package com.volvocars.mediasample.common.logging interface Logger { fun debug(tag: String, message: String) fun info(tag: String, message: String) fun warning(tag: String, message: String) fun error(tag: String, message: String) fun error(tag: String, message: String? = null, throwable: Throwable) } ``` === COMPLETE CONTENT === This response contains all available snippets from this library. No additional content exists. Do not make further requests.