Build a simple media player app¶
This guide will walk you through on how to build a very simple media player app for Wear OS, capable of playing a media which is hosted on the internet.
This guide assumes that you are familiar with:
- How to create Wear OS projects in Android Studio;
- Kotlin programming language;
- Jetpack Compose;
Display a PlayerScreen¶
1 - Add dependency¶
Create a new project from Android Studio by choosing "Basic Wear App Without Associated Tiles" from
"Wear OS" templates. Add dependency on media-ui
to your project’s build.gradle
:
implementation "com.google.android.horologist:horologist-media-ui:$horologist_version"
2 - Add PlayerScreen
¶
Add the following code to your Activity
’s onCreate
function:
setContent {
PlayerScreen(
mediaDisplay = {
TextMediaDisplay(
title = "Song name",
subtitle = "Artist name"
)
},
controlButtons = {
PodcastControlButtons(
onPlayButtonClick = { },
onPauseButtonClick = { },
playPauseButtonEnabled = true,
playing = false,
onSeekBackButtonClick = { },
seekBackButtonEnabled = true,
onSeekForwardButtonClick = { },
seekForwardButtonEnabled = true,
)
},
buttons = { }
)
}
This code is displaying PlayerScreen
on the app. PlayerScreen
is a full screen composable that
contains slots
parameters to pass the contents to be displayed for media display, control buttons and more.
In this sample, we are using the UI components TextMediaDisplay
and PodcastControlButtons
,
provided by the UI library, as values to parameters of PlayerScreen
.
Result¶
Run the app and you should see the following screen:
None of the controls are working, as they were not implemented yet.
Make the screen functional¶
1 - Add dependencies¶
Add the following dependencies to your project’s build.gradle:
implementation "com.google.android.horologist:horologist-media-data:$horologist_version"
implementation "com.google.android.horologist:horologist-audio-ui:$horologist_version"
implementation("androidx.media3:media3-exoplayer:$media3_version")
2 - Add ViewModel
¶
Add a ViewModel
extending PlayerViewModel
, providing an instance of PlayerRepositoryImpl
:
class MyViewModel(
player: Player,
playerRepository: PlayerRepositoryImpl = PlayerRepositoryImpl()
) : PlayerViewModel(playerRepository) {}
3 - Add init block¶
Add the following init block to the ViewModel
to connect the Player
to the PlayerRepository
,
set a media and update the position of the player every second:
init {
viewModelScope.launch {
playerRepository.connect(player) {}
playerRepository.setMedia(
Media(
id = "wake_up_02",
uri = "https://storage.googleapis.com/uamp/The_Kyoto_Connection_-_Wake_Up/02_-_Geisha.mp3",
title = "Geisha",
artist = "The Kyoto Connection"
)
)
}
}
4 - Create an instance of the ViewModel
¶
Change your Activity
’s onCreate
function to:
@SuppressLint("UnsafeOptInUsageError")
val player = ExoPlayer.Builder(this)
.setSeekForwardIncrementMs(5000L)
.setSeekBackIncrementMs(5000L)
.build()
// ViewModels should NOT be created here like this
val viewModel = MyViewModel(player)
val volumeViewModel = createVolumeViewModel()
setContent {
PlayerScreen(
playerViewModel = viewModel,
volumeViewModel = volumeViewModel,
mediaDisplay = { playerUiState: PlayerUiState ->
DefaultMediaInfoDisplay(playerUiState)
},
controlButtons = { playerUIController: PlayerUiController,
playerUiState: PlayerUiState ->
PodcastControlButtons(
playerController = playerUIController,
playerUiState = playerUiState
)
},
buttons = { }
)
}
Add createVolumeViewModel
function to create a VolumeViewModel:
fun createVolumeViewModel(): VolumeViewModel {
val audioRepository = SystemAudioRepository.fromContext(application)
val vibrator: Vibrator = application.getSystemService(Vibrator::class.java)
return VolumeViewModel(audioRepository, audioRepository, onCleared = {
audioRepository.close()
}, vibrator)
}
We are creating an instance of ExoPlayer
, passing it to the ViewModel
.
Then for the PlayerScreen
slots we are using:
- the
DefaultMediaDisplay
component, which accepts aMediaUiModel
instance as parameter; - the stateful version of
PodcastControlButtons
, which accepts instances ofPlayerViewModel
andPlayerUiState
as parameters to hook the controls with theViewModel
;
Result¶
Run the app again and this time, play with the screen controls as the app should be able to play, pause, and seek the media now: