Add demos/session-automotive module

This change also enables Android Auto support for the
session demo.

PiperOrigin-RevId: 569448376
This commit is contained in:
bachinger 2023-09-29 03:58:29 -07:00 committed by Copybara-Service
parent a3b1661513
commit cbd23925d5
26 changed files with 856 additions and 11 deletions

View File

@ -16,6 +16,8 @@
* Muxers: * Muxers:
* IMA extension: * IMA extension:
* Session: * Session:
* Add session demo module for Automotive OS and enable session demo for
Android Auto.
* UI: * UI:
* Downloads: * Downloads:
* OkHttp Extension: * OkHttp Extension:
@ -111,7 +113,7 @@ This release includes the following changes since the
* Add `MediaLibrarySession.getSubscribedControllers(mediaId)` for * Add `MediaLibrarySession.getSubscribedControllers(mediaId)` for
convenience. convenience.
* Override `MediaLibrarySession.Callback.onSubscribe()` to assert the * Override `MediaLibrarySession.Callback.onSubscribe()` to assert the
availability of the parent Id for which the controller subscribes. If availability of the parent ID for which the controller subscribes. If
successful, the subscription is accepted and `notifyChildrenChanged()` successful, the subscription is accepted and `notifyChildrenChanged()`
is called immediately to inform the browser is called immediately to inform the browser
([#561](https://github.com/androidx/media/issues/561)). ([#561](https://github.com/androidx/media/issues/561)).

View File

@ -15,6 +15,8 @@ project.ext {
releaseVersion = '1.2.0-alpha02' releaseVersion = '1.2.0-alpha02'
releaseVersionCode = 1_002_000_0_02 releaseVersionCode = 1_002_000_0_02
minSdkVersion = 16 minSdkVersion = 16
// See https://developer.android.com/training/cars/media/automotive-os#automotive-module
automotiveMinSdkVersion = 28
appTargetSdkVersion = 34 appTargetSdkVersion = 34
// Upgrading this requires [Internal ref: b/193254928] to be fixed, or some // Upgrading this requires [Internal ref: b/193254928] to be fixed, or some
// additional robolectric config. // additional robolectric config.

View File

@ -30,6 +30,11 @@
android:theme="@style/Theme.Media3Demo" android:theme="@style/Theme.Media3Demo"
tools:replace="android:name"> tools:replace="android:name">
<!-- Declare that this session demo supports Android Auto. -->
<meta-data
android:name="com.google.android.gms.car.application"
android:resource="@xml/auto_app_desc" />
<activity <activity
android:name=".MainActivity" android:name=".MainActivity"
android:exported="true"> android:exported="true">

View File

@ -0,0 +1,18 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Copyright 2023 The Android Open Source Project
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
-->
<automotiveApp>
<uses name="media" />
</automotiveApp>

View File

@ -0,0 +1,7 @@
# Media3 Automotive session demo
This app demonstrates use of the `MediaLibraryService` for
[Android Automotive](https://developer.android.com/training/cars/media/automotive-os).
See the [demos README](../README.md) for instructions on how to build and run
this demo.

View File

@ -0,0 +1,67 @@
// Copyright 2023 The Android Open Source Project
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
apply from: '../../constants.gradle'
apply plugin: 'com.android.application'
apply plugin: 'kotlin-android'
android {
namespace 'androidx.media3.demo.session.automotive'
compileSdkVersion project.ext.compileSdkVersion
compileOptions {
sourceCompatibility JavaVersion.VERSION_1_8
targetCompatibility JavaVersion.VERSION_1_8
}
kotlinOptions {
jvmTarget = '1.8'
}
defaultConfig {
versionName project.ext.releaseVersion
versionCode project.ext.releaseVersionCode
minSdkVersion project.ext.automotiveMinSdkVersion
targetSdkVersion project.ext.appTargetSdkVersion
multiDexEnabled true
}
buildTypes {
release {
shrinkResources true
minifyEnabled true
proguardFiles = [
getDefaultProguardFile('proguard-android-optimize.txt')
]
signingConfig signingConfigs.debug
}
debug {
jniDebuggable = true
}
}
lintOptions {
// The demo app isn't indexed, and doesn't have translations.
disable 'GoogleAppIndexingWarning','MissingTranslation'
}
}
dependencies {
implementation 'androidx.core:core-ktx:' + androidxCoreVersion
implementation 'androidx.appcompat:appcompat:' + androidxAppCompatVersion
implementation 'androidx.multidex:multidex:' + androidxMultidexVersion
implementation 'com.google.android.material:material:' + androidxMaterialVersion
implementation project(modulePrefix + 'lib-session')
implementation project(modulePrefix + 'demo-session-service')
}

View File

@ -0,0 +1,72 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Copyright 2023 The Android Open Source Project
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
-->
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
package="androidx.media3.demo.session.automotive">
<uses-sdk/>
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
<uses-permission android:name="android.permission.FOREGROUND_SERVICE_MEDIA_PLAYBACK" />
<uses-feature
android:name="android.hardware.type.automotive"
android:required="true" />
<uses-feature
android:name="android.hardware.wifi"
android:required="false" />
<uses-feature
android:name="android.hardware.screen.portrait"
android:required="false" />
<uses-feature
android:name="android.hardware.screen.landscape"
android:required="false" />
<meta-data android:name="com.android.automotive"
android:resource="@xml/automotive_app_desc"/>
<application
android:name="androidx.multidex.MultiDexApplication"
android:allowBackup="false"
android:taskAffinity=""
android:appCategory="audio"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
tools:replace="android:name">
<meta-data
android:name="androidx.car.app.TintableAttributionIcon"
android:resource="@mipmap/ic_launcher" />
<service
android:name=".AutomotiveService"
android:foregroundServiceType="mediaPlayback"
android:exported="true">
<intent-filter>
<action android:name="androidx.media3.session.MediaLibraryService"/>
<action android:name="android.media.browse.MediaBrowserService"/>
</intent-filter>
</service>
<!-- Artwork provider for content:// URIs -->
<provider
android:name="BitmapContentProvider"
android:authorities="androidx.media3"
android:exported="true" />
</application>
</manifest>

Binary file not shown.

After

Width:  |  Height:  |  Size: 353 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 215 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 41 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 278 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 240 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 425 KiB

View File

@ -0,0 +1,472 @@
{
"media": [
{
"id": "wake_up_01",
"title": "Intro - The Way Of Waking Up (feat. Alan Watts)",
"album": "Wake Up",
"artist": "The Kyoto Connection",
"genre": "Electronic",
"source": "https://storage.googleapis.com/uamp/The_Kyoto_Connection_-_Wake_Up/01_-_Intro_-_The_Way_Of_Waking_Up_feat_Alan_Watts.mp3",
"image": "content://androidx.media3/artwork/album_kyoto_connection.png",
"trackNumber": 1,
"totalTrackCount": 13,
"duration": 90,
"site": "http://freemusicarchive.org/music/The_Kyoto_Connection/Wake_Up_1957/"
},
{
"id": "wake_up_02",
"title": "Geisha",
"album": "Wake Up",
"artist": "The Kyoto Connection",
"genre": "Electronic",
"source": "https://storage.googleapis.com/uamp/The_Kyoto_Connection_-_Wake_Up/02_-_Geisha.mp3",
"image": "content://androidx.media3/artwork/album_kyoto_connection.png",
"trackNumber": 2,
"totalTrackCount": 13,
"duration": 267,
"site": "http://freemusicarchive.org/music/The_Kyoto_Connection/Wake_Up_1957/"
},
{
"id": "wake_up_03",
"title": "Voyage I - Waterfall",
"album": "Wake Up",
"artist": "The Kyoto Connection",
"genre": "Electronic",
"source": "https://storage.googleapis.com/uamp/The_Kyoto_Connection_-_Wake_Up/03_-_Voyage_I_-_Waterfall.mp3",
"image": "content://androidx.media3/artwork/album_kyoto_connection.png",
"trackNumber": 3,
"totalTrackCount": 13,
"duration": 264,
"site": "http://freemusicarchive.org/music/The_Kyoto_Connection/Wake_Up_1957/"
},
{
"id": "wake_up_04",
"title": "The Music In You",
"album": "Wake Up",
"artist": "The Kyoto Connection",
"genre": "Electronic",
"source": "https://storage.googleapis.com/uamp/The_Kyoto_Connection_-_Wake_Up/04_-_The_Music_In_You.mp3",
"image": "content://androidx.media3/artwork/album_kyoto_connection.png",
"trackNumber": 4,
"totalTrackCount": 13,
"duration": 223,
"site": "http://freemusicarchive.org/music/The_Kyoto_Connection/Wake_Up_1957/"
},
{
"id": "wake_up_05",
"title": "The Calm Before The Storm",
"album": "Wake Up",
"artist": "The Kyoto Connection",
"genre": "Electronic",
"source": "https://storage.googleapis.com/uamp/The_Kyoto_Connection_-_Wake_Up/05_-_The_Calm_Before_The_Storm.mp3",
"image": "content://androidx.media3/artwork/album_kyoto_connection.png",
"trackNumber": 5,
"totalTrackCount": 13,
"duration": 229,
"site": "http://freemusicarchive.org/music/The_Kyoto_Connection/Wake_Up_1957/"
},
{
"id": "wake_up_06",
"title": "No Pain, No Gain",
"album": "Wake Up",
"artist": "The Kyoto Connection",
"genre": "Electronic",
"source": "https://storage.googleapis.com/uamp/The_Kyoto_Connection_-_Wake_Up/06_-_No_Pain_No_Gain.mp3",
"image": "content://androidx.media3/artwork/album_kyoto_connection.png",
"trackNumber": 6,
"totalTrackCount": 13,
"duration": 304,
"site": "http://freemusicarchive.org/music/The_Kyoto_Connection/Wake_Up_1957/"
},
{
"id": "wake_up_07",
"title": "Voyage II - Satori",
"album": "Wake Up",
"artist": "The Kyoto Connection",
"genre": "Electronic",
"source": "https://storage.googleapis.com/uamp/The_Kyoto_Connection_-_Wake_Up/07_-_Voyage_II_-_Satori.mp3",
"image": "content://androidx.media3/artwork/album_kyoto_connection.png",
"trackNumber": 7,
"totalTrackCount": 13,
"duration": 256,
"site": "http://freemusicarchive.org/music/The_Kyoto_Connection/Wake_Up_1957/"
},
{
"id": "wake_up_08",
"title": "Reveal the Magic",
"album": "Wake Up",
"artist": "The Kyoto Connection",
"genre": "Electronic",
"source": "https://storage.googleapis.com/uamp/The_Kyoto_Connection_-_Wake_Up/08_-_Reveal_the_Magic.mp3",
"image": "content://androidx.media3/artwork/album_kyoto_connection.png",
"trackNumber": 8,
"totalTrackCount": 13,
"duration": 293,
"site": "http://freemusicarchive.org/music/The_Kyoto_Connection/Wake_Up_1957/"
},
{
"id": "wake_up_09",
"title": "Hachiko (The Faithtful Dog)",
"album": "Wake Up",
"artist": "The Kyoto Connection",
"genre": "Electronic",
"source": "https://storage.googleapis.com/uamp/The_Kyoto_Connection_-_Wake_Up/09_-_Hachiko_The_Faithtful_Dog.mp3",
"image": "content://androidx.media3/artwork/album_kyoto_connection.png",
"trackNumber": 9,
"totalTrackCount": 13,
"duration": 185,
"site": "http://freemusicarchive.org/music/The_Kyoto_Connection/Wake_Up_1957/"
},
{
"id": "wake_up_10",
"title": "Wake Up",
"album": "Wake Up",
"artist": "The Kyoto Connection",
"genre": "Electronic",
"source": "https://storage.googleapis.com/uamp/The_Kyoto_Connection_-_Wake_Up/10_-_Wake_Up.mp3",
"image": "content://androidx.media3/artwork/album_kyoto_connection.png",
"trackNumber": 10,
"totalTrackCount": 13,
"duration": 251,
"site": "http://freemusicarchive.org/music/The_Kyoto_Connection/Wake_Up_1957/"
},
{
"id": "wake_up_11",
"title": "Voyage III - The Space Between Us",
"album": "Wake Up",
"artist": "The Kyoto Connection",
"genre": "Electronic",
"source": "https://storage.googleapis.com/uamp/The_Kyoto_Connection_-_Wake_Up/11_-_Voyage_III_-_The_Space_Between_Us.mp3",
"image": "content://androidx.media3/artwork/album_kyoto_connection.png",
"trackNumber": 11,
"totalTrackCount": 13,
"duration": 290,
"site": "http://freemusicarchive.org/music/The_Kyoto_Connection/Wake_Up_1957/"
},
{
"id": "wake_up_12",
"title": "Ume No Kaori (feat. Sunawai)",
"album": "Wake Up",
"artist": "The Kyoto Connection",
"genre": "Electronic",
"source": "https://storage.googleapis.com/uamp/The_Kyoto_Connection_-_Wake_Up/12_-_Ume_No_Kaori_feat_Sunawai.mp3",
"image": "content://androidx.media3/artwork/album_kyoto_connection.png",
"trackNumber": 12,
"totalTrackCount": 13,
"duration": 334,
"site": "http://freemusicarchive.org/music/The_Kyoto_Connection/Wake_Up_1957/"
},
{
"id": "wake_up_13",
"title": "Outro - Totally Here and Now (feat. Alan Watts)",
"album": "Wake Up",
"artist": "The Kyoto Connection",
"genre": "Electronic",
"source": "https://storage.googleapis.com/uamp/The_Kyoto_Connection_-_Wake_Up/13_-_Outro_-_Totally_Here_and_Now_feat_Alan_Watts.mp3",
"image": "content://androidx.media3/artwork/album_kyoto_connection.png",
"trackNumber": 13,
"totalTrackCount": 13,
"duration": 242,
"site": "http://freemusicarchive.org/music/The_Kyoto_Connection/Wake_Up_1957/"
},
{
"id": "irsens_tale_01",
"title": "Intro (.udonthear)",
"album": "Irsen's Tale",
"artist": "Kai Engel",
"genre": "Ambient",
"source": "https://storage.googleapis.com/uamp/Kai_Engel_-_Irsens_Tale/01_-_Intro_udonthear.mp3",
"image": "content://androidx.media3/artwork/album_kai_engel.png",
"trackNumber": 1,
"totalTrackCount": 9,
"duration": 63,
"site": "http://freemusicarchive.org/music/Kai_Engel/Irsens_Tale/"
},
{
"id": "irsens_tale_02",
"title": "Leaving",
"album": "Irsen's Tale",
"artist": "Kai Engel",
"genre": "Ambient",
"source": "https://storage.googleapis.com/uamp/Kai_Engel_-_Irsens_Tale/02_-_Leaving.mp3",
"image": "content://androidx.media3/artwork/album_kai_engel.png",
"trackNumber": 2,
"totalTrackCount": 9,
"duration": 170,
"site": "http://freemusicarchive.org/music/Kai_Engel/Irsens_Tale/"
},
{
"id": "irsens_tale_03",
"title": "Irsen's Tale",
"album": "Irsen's Tale",
"artist": "Kai Engel",
"genre": "Ambient",
"source": "https://storage.googleapis.com/uamp/Kai_Engel_-_Irsens_Tale/03_-_Irsens_Tale.mp3",
"image": "content://androidx.media3/artwork/album_kai_engel.png",
"trackNumber": 3,
"totalTrackCount": 9,
"duration": 164,
"site": "http://freemusicarchive.org/music/Kai_Engel/Irsens_Tale/"
},
{
"id": "irsens_tale_04",
"title": "Moonlight Reprise",
"album": "Irsen's Tale",
"artist": "Kai Engel",
"genre": "Ambient",
"source": "https://storage.googleapis.com/uamp/Kai_Engel_-_Irsens_Tale/04_-_Moonlight_Reprise.mp3",
"image": "content://androidx.media3/artwork/album_kai_engel.png",
"trackNumber": 4,
"totalTrackCount": 9,
"duration": 181,
"site": "http://freemusicarchive.org/music/Kai_Engel/Irsens_Tale/"
},
{
"id": "irsens_tale_05",
"title": "Nothing Lasts Forever",
"album": "Irsen's Tale",
"artist": "Kai Engel",
"genre": "Ambient",
"source": "https://storage.googleapis.com/uamp/Kai_Engel_-_Irsens_Tale/05_-_Nothing_Lasts_Forever.mp3",
"image": "content://androidx.media3/artwork/album_kai_engel.png",
"trackNumber": 5,
"totalTrackCount": 9,
"duration": 132,
"site": "http://freemusicarchive.org/music/Kai_Engel/Irsens_Tale/"
},
{
"id": "irsens_tale_06",
"title": "The Moments of Our Mornings",
"album": "Irsen's Tale",
"artist": "Kai Engel",
"genre": "Ambient",
"source": "https://storage.googleapis.com/uamp/Kai_Engel_-_Irsens_Tale/06_-_The_Moments_of_Our_Mornings.mp3",
"image": "content://androidx.media3/artwork/album_kai_engel.png",
"trackNumber": 6,
"totalTrackCount": 9,
"duration": 104,
"site": "http://freemusicarchive.org/music/Kai_Engel/Irsens_Tale/"
},
{
"id": "irsens_tale_07",
"title": "Laceration",
"album": "Irsen's Tale",
"artist": "Kai Engel",
"genre": "Ambient",
"source": "https://storage.googleapis.com/uamp/Kai_Engel_-_Irsens_Tale/07_-_Laceration.mp3",
"image": "content://androidx.media3/artwork/album_kai_engel.png",
"trackNumber": 7,
"totalTrackCount": 9,
"duration": 173,
"site": "http://freemusicarchive.org/music/Kai_Engel/Irsens_Tale/"
},
{
"id": "irsens_tale_08",
"title": "Memories",
"album": "Irsen's Tale",
"artist": "Kai Engel",
"genre": "Ambient",
"source": "https://storage.googleapis.com/uamp/Kai_Engel_-_Irsens_Tale/08_-_Memories.mp3",
"image": "content://androidx.media3/artwork/album_kai_engel.png",
"trackNumber": 8,
"totalTrackCount": 9,
"duration": 213,
"site": "http://freemusicarchive.org/music/Kai_Engel/Irsens_Tale/"
},
{
"id": "irsens_tale_09",
"title": "Outro",
"album": "Irsen's Tale",
"artist": "Kai Engel",
"genre": "Ambient",
"source": "https://storage.googleapis.com/uamp/Kai_Engel_-_Irsens_Tale/09_-_Outro.mp3",
"image": "content://androidx.media3/artwork/album_kai_engel.png",
"trackNumber": 9,
"totalTrackCount": 9,
"duration": 65,
"site": "http://freemusicarchive.org/music/Kai_Engel/Irsens_Tale/"
},
{
"id": "jazz_in_paris",
"title": "Jazz in Paris",
"album": "Jazz & Blues",
"artist": "Media Right Productions",
"genre": "Jazz & Blues",
"source": "https://storage.googleapis.com/automotive-media/Jazz_In_Paris.mp3",
"image": "content://androidx.media3/artwork/album_sea.png",
"trackNumber": 1,
"totalTrackCount": 6,
"duration": 103,
"site": "https://www.youtube.com/audiolibrary/music"
},
{
"id": "the_messenger",
"title": "The Messenger",
"album": "Jazz & Blues",
"artist": "Silent Partner",
"genre": "Jazz & Blues",
"source": "https://storage.googleapis.com/automotive-media/The_Messenger.mp3",
"image": "content://androidx.media3/artwork/album_sea.png",
"trackNumber": 2,
"totalTrackCount": 6,
"duration": 132,
"site": "https://www.youtube.com/audiolibrary/music"
},
{
"id": "talkies",
"title": "Talkies",
"album": "Jazz & Blues",
"artist": "Huma-Huma",
"genre": "Jazz & Blues",
"source": "https://storage.googleapis.com/automotive-media/Talkies.mp3",
"image": "content://androidx.media3/artwork/album_sea.png",
"trackNumber": 3,
"totalTrackCount": 6,
"duration": 162,
"site": "https://www.youtube.com/audiolibrary/music"
},
{
"id": "on_the_bach",
"title": "On the Bach",
"album": "Cinematic",
"artist": "Jingle Punks",
"genre": "Cinematic",
"source": "https://storage.googleapis.com/automotive-media/On_the_Bach.mp3",
"image": "content://androidx.media3/artwork/album_drinks.png",
"trackNumber": 4,
"totalTrackCount": 6,
"duration": 66,
"site": "https://www.youtube.com/audiolibrary/music"
},
{
"id": "the_story_unfolds",
"title": "The Story Unfolds",
"album": "Cinematic",
"artist": "Jingle Punks",
"genre": "Cinematic",
"source": "https://storage.googleapis.com/automotive-media/The_Story_Unfolds.mp3",
"image": "content://androidx.media3/artwork/album_drinks.png",
"trackNumber": 5,
"totalTrackCount": 6,
"duration": 91,
"site": "https://www.youtube.com/audiolibrary/music"
},
{
"id": "drop_and_roll",
"title": "Drop and Roll",
"album": "Youtube Audio Library Rock",
"artist": "Silent Partner",
"genre": "Rock",
"source": "https://storage.googleapis.com/automotive-media/Drop_and_Roll.mp3",
"image": "content://androidx.media3/artwork/album_road.png",
"trackNumber": 1,
"totalTrackCount": 7,
"duration": 121,
"site": "https://www.youtube.com/audiolibrary/music"
},
{
"id": "motocross",
"title": "Motocross",
"album": "Youtube Audio Library Rock",
"artist": "Topher Mohr and Alex Elena",
"genre": "Rock",
"source": "https://storage.googleapis.com/automotive-media/Motocross.mp3",
"image": "content://androidx.media3/artwork/album_road.png",
"trackNumber": 2,
"totalTrackCount": 7,
"duration": 182,
"site": "https://www.youtube.com/audiolibrary/music"
},
{
"id": "wish_youd_come_true",
"title": "Wish You'd Come True",
"album": "Youtube Audio Library Rock",
"artist": "The 126ers",
"genre": "Rock",
"source": "https://storage.googleapis.com/automotive-media/Wish_You_d_Come_True.mp3",
"image": "content://androidx.media3/artwork/album_road.png",
"trackNumber": 3,
"totalTrackCount": 7,
"duration": 169,
"site": "https://www.youtube.com/audiolibrary/music"
},
{
"id": "awakening",
"title": "Awakening",
"album": "Youtube Audio Library Rock",
"artist": "Silent Partner",
"genre": "Rock",
"source": "https://storage.googleapis.com/automotive-media/Awakening.mp3",
"image": "content://androidx.media3/artwork/album_road.png",
"trackNumber": 4,
"totalTrackCount": 7,
"duration": 220,
"site": "https://www.youtube.com/audiolibrary/music"
},
{
"id": "home",
"title": "Home",
"album": "Youtube Audio Library Rock",
"artist": "Letter Box",
"genre": "Rock",
"source": "https://storage.googleapis.com/automotive-media/Home.mp3",
"image": "content://androidx.media3/artwork/album_road.png",
"trackNumber": 5,
"totalTrackCount": 7,
"duration": 213,
"site": "https://www.youtube.com/audiolibrary/music"
},
{
"id": "tell_the_angels",
"title": "Tell The Angels",
"album": "Youtube Audio Library Rock 2",
"artist": "Letter Box",
"genre": "Rock",
"source": "https://storage.googleapis.com/automotive-media/Tell_The_Angels.mp3",
"image": "content://androidx.media3/artwork/album_skyline.png",
"trackNumber": 6,
"totalTrackCount": 7,
"duration": 208,
"site": "https://www.youtube.com/audiolibrary/music"
},
{
"id": "hey_sailor",
"title": "Hey Sailor",
"album": "Youtube Audio Library Rock 2",
"artist": "Letter Box",
"genre": "Rock",
"source": "https://storage.googleapis.com/automotive-media/Hey_Sailor.mp3",
"image": "content://androidx.media3/artwork/album_skyline.png",
"trackNumber": 7,
"totalTrackCount": 7,
"duration": 193,
"site": "https://www.youtube.com/audiolibrary/music"
},
{
"id": "keys_to_the_kingdom",
"title": "Keys To The Kingdom",
"album": "Youtube Audio Library Rock 2",
"artist": "The 126ers",
"genre": "Rock",
"source": "https://storage.googleapis.com/automotive-media/Keys_To_The_Kingdom.mp3",
"image": "content://androidx.media3/artwork/album_skyline.png",
"trackNumber": 1,
"totalTrackCount": 2,
"duration": 221,
"site": "https://www.youtube.com/audiolibrary/music"
},
{
"id": "the_coldest_shoulder",
"title": "The Coldest Shoulder",
"album": "Youtube Audio Library Rock 2",
"artist": "The 126ers",
"genre": "Rock",
"source": "https://storage.googleapis.com/automotive-media/The_Coldest_Shoulder.mp3",
"image": "content://androidx.media3/artwork/album_skyline.png",
"trackNumber": 2,
"totalTrackCount": 2,
"duration": 160,
"site": "https://www.youtube.com/audiolibrary/music"
}
]
}

View File

@ -0,0 +1,59 @@
/*
* Copyright 2023 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package androidx.media3.demo.session.automotive
import androidx.annotation.OptIn
import androidx.media3.common.MediaItem
import androidx.media3.common.util.UnstableApi
import androidx.media3.demo.session.DemoMediaLibrarySessionCallback
import androidx.media3.demo.session.DemoPlaybackService
import androidx.media3.session.LibraryResult
import androidx.media3.session.MediaConstants
import androidx.media3.session.MediaSession.ControllerInfo
import com.google.common.util.concurrent.ListenableFuture
class AutomotiveService : DemoPlaybackService() {
override fun createLibrarySessionCallback(): MediaLibrarySession.Callback {
return object : DemoMediaLibrarySessionCallback(this@AutomotiveService) {
@OptIn(UnstableApi::class)
override fun onGetLibraryRoot(
session: MediaLibrarySession,
browser: ControllerInfo,
params: LibraryParams?
): ListenableFuture<LibraryResult<MediaItem>> {
var responseParams = params
if (session.isAutomotiveController(browser)) {
// See https://developer.android.com/training/cars/media#apply_content_style
val rootHintParams = params ?: LibraryParams.Builder().build()
rootHintParams.extras.putInt(
MediaConstants.EXTRAS_KEY_CONTENT_STYLE_BROWSABLE,
MediaConstants.EXTRAS_VALUE_CONTENT_STYLE_GRID_ITEM
)
rootHintParams.extras.putInt(
MediaConstants.EXTRAS_KEY_CONTENT_STYLE_PLAYABLE,
MediaConstants.EXTRAS_VALUE_CONTENT_STYLE_LIST_ITEM
)
// Tweaked params are propagated to Automotive browsers as root hints.
responseParams = rootHintParams
}
// Use super to return the common library root with the tweaked params sent to the browser.
return super.onGetLibraryRoot(session, browser, responseParams)
}
}
}
}

View File

@ -0,0 +1,89 @@
/*
* Copyright 2023 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package androidx.media3.demo.session.automotive
import android.content.ContentProvider
import android.content.ContentValues
import android.content.Context
import android.database.Cursor
import android.net.Uri
import android.os.ParcelFileDescriptor
import java.io.File
/**
* Provides artwork for content URIs.
*
* <p>A bitmap file in the asset folder with path 'artwork/album1.png' can be referenced as artwork
* URI with 'content://androidx.media3/artwork/album1.png'. 'androidx.media3' is the authority
* declared for the content provider in 'AndroidManifest.xml'.
*
* <p>For demo use only.
*/
class BitmapContentProvider : ContentProvider() {
override fun onCreate() = true
override fun openFile(uri: Uri, mode: String): ParcelFileDescriptor? {
context?.let { ctx ->
getAssetPath(uri)?.let {
return ParcelFileDescriptor.open(
copyAssetFileToCacheDirectory(ctx, it),
ParcelFileDescriptor.MODE_READ_ONLY
)
}
}
return super.openFile(uri, mode)
}
private fun getAssetPath(contentUri: Uri): String? {
contentUri.path?.let {
return it.substring(1)
}
return null
}
private fun copyAssetFileToCacheDirectory(context: Context, assetPath: String): File {
val publicFile = File(context.cacheDir, assetPath.replace("/", "_"))
if (!publicFile.exists()) {
context.assets.open(assetPath).copyTo(publicFile.outputStream())
}
return publicFile
}
// No-op implementations of abstract ContentProvider methods.
override fun insert(uri: Uri, values: ContentValues?): Uri? = null
override fun query(
uri: Uri,
projection: Array<String>?,
selection: String?,
selectionArgs: Array<String>?,
sortOrder: String?
): Cursor? = null
override fun update(
uri: Uri,
values: ContentValues?,
selection: String?,
selectionArgs: Array<String>?
) = 0
override fun delete(uri: Uri, selection: String?, selectionArgs: Array<String>?) = 0
override fun getType(uri: Uri): String? = null
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 10 KiB

View File

@ -0,0 +1,18 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Copyright 2023 The Android Open Source Project
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
-->
<resources>
<string name="app_name">Media3 Automotive Demo</string>
</resources>

View File

@ -0,0 +1,18 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Copyright 2023 The Android Open Source Project
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
-->
<automotiveApp>
<uses name="media"/>
</automotiveApp>

View File

@ -44,12 +44,12 @@ open class DemoMediaLibrarySessionCallback(private val context: Context) :
CommandButton.Builder() CommandButton.Builder()
.setDisplayName(context.getString(R.string.exo_controls_shuffle_on_description)) .setDisplayName(context.getString(R.string.exo_controls_shuffle_on_description))
.setSessionCommand(SessionCommand(CUSTOM_COMMAND_TOGGLE_SHUFFLE_MODE_ON, Bundle.EMPTY)) .setSessionCommand(SessionCommand(CUSTOM_COMMAND_TOGGLE_SHUFFLE_MODE_ON, Bundle.EMPTY))
.setIconResId(R.drawable.exo_icon_shuffle_on) .setIconResId(R.drawable.exo_icon_shuffle_off)
.build(), .build(),
CommandButton.Builder() CommandButton.Builder()
.setDisplayName(context.getString(R.string.exo_controls_shuffle_off_description)) .setDisplayName(context.getString(R.string.exo_controls_shuffle_off_description))
.setSessionCommand(SessionCommand(CUSTOM_COMMAND_TOGGLE_SHUFFLE_MODE_OFF, Bundle.EMPTY)) .setSessionCommand(SessionCommand(CUSTOM_COMMAND_TOGGLE_SHUFFLE_MODE_OFF, Bundle.EMPTY))
.setIconResId(R.drawable.exo_icon_shuffle_off) .setIconResId(R.drawable.exo_icon_shuffle_on)
.build() .build()
) )
@ -71,7 +71,11 @@ open class DemoMediaLibrarySessionCallback(private val context: Context) :
session: MediaSession, session: MediaSession,
controller: MediaSession.ControllerInfo controller: MediaSession.ControllerInfo
): MediaSession.ConnectionResult { ): MediaSession.ConnectionResult {
if (session.isMediaNotificationController(controller)) { if (
session.isMediaNotificationController(controller) ||
session.isAutomotiveController(controller) ||
session.isAutoCompanionController(controller)
) {
// Select the button to display. // Select the button to display.
val customLayout = customLayoutCommandButtons[if (session.player.shuffleModeEnabled) 1 else 0] val customLayout = customLayoutCommandButtons[if (session.player.shuffleModeEnabled) 1 else 0]
return MediaSession.ConnectionResult.AcceptedResultBuilder(session) return MediaSession.ConnectionResult.AcceptedResultBuilder(session)
@ -90,22 +94,27 @@ open class DemoMediaLibrarySessionCallback(private val context: Context) :
customCommand: SessionCommand, customCommand: SessionCommand,
args: Bundle args: Bundle
): ListenableFuture<SessionResult> { ): ListenableFuture<SessionResult> {
if (!session.isMediaNotificationController(controller)) {
return Futures.immediateFuture(SessionResult(SessionResult.RESULT_ERROR_NOT_SUPPORTED))
}
if (CUSTOM_COMMAND_TOGGLE_SHUFFLE_MODE_ON == customCommand.customAction) { if (CUSTOM_COMMAND_TOGGLE_SHUFFLE_MODE_ON == customCommand.customAction) {
// Enable shuffling. // Enable shuffling.
session.player.shuffleModeEnabled = true session.player.shuffleModeEnabled = true
// Change the custom layout to contain the `Disable shuffling` command. // Change the custom layout to contain the `Disable shuffling` command.
session.setCustomLayout(controller, ImmutableList.of(customLayoutCommandButtons[1])) session.setCustomLayout(
session.mediaNotificationControllerInfo!!,
ImmutableList.of(customLayoutCommandButtons[1])
)
return Futures.immediateFuture(SessionResult(SessionResult.RESULT_SUCCESS))
} else if (CUSTOM_COMMAND_TOGGLE_SHUFFLE_MODE_OFF == customCommand.customAction) { } else if (CUSTOM_COMMAND_TOGGLE_SHUFFLE_MODE_OFF == customCommand.customAction) {
// Disable shuffling. // Disable shuffling.
session.player.shuffleModeEnabled = false session.player.shuffleModeEnabled = false
// Change the custom layout to contain the `Enable shuffling` command. // Change the custom layout to contain the `Enable shuffling` command.
session.setCustomLayout(controller, ImmutableList.of(customLayoutCommandButtons[0])) session.setCustomLayout(
} session.mediaNotificationControllerInfo!!,
ImmutableList.of(customLayoutCommandButtons[0])
)
return Futures.immediateFuture(SessionResult(SessionResult.RESULT_SUCCESS)) return Futures.immediateFuture(SessionResult(SessionResult.RESULT_SUCCESS))
} }
return Futures.immediateFuture(SessionResult(SessionResult.RESULT_ERROR_NOT_SUPPORTED))
}
override fun onGetLibraryRoot( override fun onGetLibraryRoot(
session: MediaLibraryService.MediaLibrarySession, session: MediaLibraryService.MediaLibrarySession,

View File

@ -224,7 +224,12 @@ object MediaItemTree {
isPlayable = true, isPlayable = true,
isBrowsable = true, isBrowsable = true,
mediaType = MediaMetadata.MEDIA_TYPE_ALBUM, mediaType = MediaMetadata.MEDIA_TYPE_ALBUM,
subtitleConfigurations subtitleConfigurations,
album = null,
artist = null,
genre = null,
sourceUri = null,
imageUri
) )
) )
treeNodes[ALBUM_ID]!!.addChild(albumFolderIdInTree) treeNodes[ALBUM_ID]!!.addChild(albumFolderIdInTree)

View File

@ -31,6 +31,8 @@ include modulePrefix + 'demo-session'
project(modulePrefix + 'demo-session').projectDir = new File(rootDir, 'demos/session') project(modulePrefix + 'demo-session').projectDir = new File(rootDir, 'demos/session')
include modulePrefix + 'demo-session-service' include modulePrefix + 'demo-session-service'
project(modulePrefix + 'demo-session-service').projectDir = new File(rootDir, 'demos/session_service') project(modulePrefix + 'demo-session-service').projectDir = new File(rootDir, 'demos/session_service')
include modulePrefix + 'demo-session-automotive'
project(modulePrefix + 'demo-session-automotive').projectDir = new File(rootDir, 'demos/session_automotive')
include modulePrefix + 'demo-surface' include modulePrefix + 'demo-surface'
project(modulePrefix + 'demo-surface').projectDir = new File(rootDir, 'demos/surface') project(modulePrefix + 'demo-surface').projectDir = new File(rootDir, 'demos/surface')
include modulePrefix + 'demo-transformer' include modulePrefix + 'demo-transformer'