템플릿이 적용된 미디어 앱 빌드

템플릿 미디어 앱은 베타 버전입니다.
현재 누구나 Google Play 스토어의 내부 테스트 및 비공개 테스트 트랙에 템플릿 미디어 앱을 게시할 수 있습니다. 공개 트랙 및 프로덕션 트랙에 게시하는 것은 추후 허용될 예정입니다.

자동차 앱 라이브러리 템플릿을 사용하는 미디어 앱은 자동차 화면에 최적화되고 운전 중 주의 산만을 최소화하는 환경을 보장하면서 미디어 탐색 및 재생 환경을 맞춤설정할 수 있습니다.

이 가이드에서는 개발자에게 이미 휴대전화에서 오디오를 재생하는 미디어 앱이 있고 미디어 앱이 Android 미디어 앱 아키텍처를 준수한다고 가정합니다. 자동차용 미디어 앱 빌드 MediaBrowser 데이터 구조를 사용하여 빌드된 앱 내 환경 대신 템플릿을 사용할 수 있는 기능이 자동차 앱 라이브러리에 제공됩니다. 재생 컨트롤을 위한 MediaSession과 추천 및 기타 스마트 환경에 사용되는 MediaBrowserService 또는 MediaLibraryService는 계속 제공해야 합니다.

앱의 매니페스트 구성

자동차용 Android 앱 라이브러리 사용에 설명된 단계 외에도 템플릿 미디어 앱에는 다음이 필요합니다.

매니페스트에서 카테고리 지원 선언

앱은 androidx.car.app.category.MEDIA 자동차 앱 카테고리CarAppService의 인텐트 필터에서 선언해야 합니다.

<application>
    ...
   <service
       ...
        android:name=".MyCarAppService"
        android:exported="true">
      <intent-filter>
        <action android:name="androidx.car.app.CarAppService" />
        <category android:name="androidx.car.app.category.MEDIA"/>
      </intent-filter>
    </service>
    ...
<application>

MediaPlaybackTemplate에 액세스하려면 앱이 매니페스트 파일에서 androidx.car.app.MEDIA_TEMPLATES 권한도 선언해야 합니다.

<manifest ...>
  ...
  <uses-permission android:name="androidx.car.app.MEDIA_TEMPLATES"/>
  ...
</manifest>

최소 자동차 앱 API 수준 설정

MediaPlaybackTemplate을 사용하는 미디어 앱은 CAL API 8 이상에서만 지원되므로 최소 Car App API level이 8로 설정되어 있는지 확인하세요.

<application ...>
  ...
  <meta-data
    android:name="androidx.car.app.minCarApiLevel"
    android:value="8"/>
  ...
</application>

저작자 표시 아이콘 제공

자동차 앱 라이브러리를 사용하여 빌드된 미디어 앱의 저작자 표시 아이콘을 추가해야 합니다.

Android Auto 지원 선언

앱의 매니페스트에 다음이 포함되어 있는지 확인합니다.

<application>
  ...
  <meta-data android:name="com.google.android.gms.car.application"
      android:resource="@xml/automotive_app_desc"/>
  ...
</application>

그런 다음 xml 리소스의 automotive_app_desc.xml템플릿 선언을 추가합니다. 다음과 같이 표시됩니다.

<automotiveApp xmlns:android="http://schemas.android.com/apk/res/android">
 <uses name="media"/>
 <uses name="template"/>
</automotiveApp>

Android Automotive OS 지원 선언

Android Automotive OS에서 자동차 앱 라이브러리가 사용 설정된 미디어 앱을 배포하는 방법에는 단일 APK 또는 두 개의 별도 APK가 있습니다. 단일 APK를 배포하면 자동차 앱 라이브러리 호스트로 Android Automotive OS가 사용 설정된 차량을 지원하고 이전 Android 버전 (Android 10~Android 13)의 경우에도 지원되지 않으면 MediaBrowserService 또는 MediaLibraryService 애플리케이션으로 대체됩니다. 두 개의 별도 APK를 배포하도록 선택하면 앱의 MediaBrowserService 또는 MediaLibraryService 버전에 영향을 미칠까 봐 걱정하지 않고 자동차 앱 라이브러리 버전에 새로 추가된 항목을 더 쉽게 업데이트할 수 있습니다.

단일 APK 배포

자동차 앱 라이브러리 및 앱의 MediaBrowserService 또는 MediaLibraryService 버전에 단일 APK를 배포할 때는 ""를 android:required="false"로 설정하는 것이 중요합니다.

<uses-feature android:name="android.software.car.templates_host.media" android:required="false"/>

다음으로 AAOS용 자동차 앱 라이브러리 가이드라인을 따르고 실행 가능한 CarAppActivity (또는 트램펄린 활동)를 도입합니다. 매니페스트에서 활동을 android:enabled="false"로 설정해야 합니다. 다음으로 CarAppActivity 구성요소를 대체 항목으로 나타내는 메타데이터 태그를 MediaBrowserService 선언에 추가합니다. 아래 매니페스트 예를 참고하세요.

<service android:name=".media.MyMediaService"
    android:exported="true"
    android:label="@string/app_name">
    <intent-filter>
        <action android:name="androidx.media3.session.MediaLibraryService"/>
    </intent-filter>

    <!-- Link to Car App Library Activity -->
    <meta-data
        android:name="androidx.car.app.media.CalMediaActivityComponent" 
        android:value="com.example.mediaapp.LaunchableTrampoline"/>
</service>

<activity
    android:name=".LaunchableTrampoline"
    android:exported="true"
    android:theme="@android:style/Theme.DeviceDefault.NoActionBar"
    android:launchMode="singleTask"
    android:label="@string/app_name_cal"
    android:enabled="false"> <!-- Set to false -->

    <meta-data android:name="distractionOptimized" android:value="true" />

    <intent-filter>
        <action android:name="android.intent.action.MAIN"/>
        <action android:name="androidx.car.app.media.action.SHOW_MEDIA_PLAYBACK"/>
        <category android:name="android.intent.category.LAUNCHER"/>
    </intent-filter>
</activity>

Play 배포

자동차 앱 라이브러리 및 MediaBrowserService 또는 MediaLibraryService가 포함된 APK는 버전 코드가 더 높고 Android 14 (34)를 타겟팅하는 minSdk로 사용 설정해야 합니다.

두 개의 APK로 배포

자동차 앱 라이브러리를 사용하는 APK와 MediaBrowserService 또는 MediaLibraryService를 사용하는 APK 등 두 개의 별도 APK를 배포하려면 다음 단계에 따라 올바른 차량 기능을 올바르게 타겟팅하세요.

앱의 자동차 앱 라이브러리 버전에 별도 APK를 만들 때는 android.software.car.templates_host.mediaandroid:required=true로 설정해야 합니다. 이렇게 하면 자동차 앱 라이브러리 호스트 지원으로 인증된 Android Automotive OS 빌드에만 앱이 배포됩니다.

<uses-feature android:name="android.software.car.templates_host.media" android:required="true"/>

위에서 android.software.car.templates_host.media를 사용하고 android:required=true로 설정하는 것 외에도 다음 단계에 따라 실행 가능한 자동차 앱 라이브러리 활동에 Android Automotive OS 를 사용 설정하세요.

Play 배포

자동차 앱 라이브러리를 사용하는 APK는 Automotive OS 전용 트랙에 배포해야 합니다.

음성 작업 지원

사용자가 핸즈프리로 일반적인 작업을 완료할 수 있도록 앱에 음성 기능을 사용 설정합니다. 자세한 구현 안내는 미디어용 음성 작업 지원을 참고하세요. 템플릿 미디어 앱을 사용하면 음성 명령을 수신할 때 검색 결과로 MediaBrowserService 또는 MediaLibraryService를 업데이트할 필요가 없습니다. 대신 미디어 재생 템플릿에 작업을 추가하여 사용자가 재생 또는 검색어를 기반으로 더 많은 콘텐츠를 찾을 수 있도록 하는 것이 좋습니다. 음성 명령을 지원하려면 VC-1 품질 가이드라인을 충족해야 합니다.

재생 템플릿 만들기

MediaPlaybackTemplate은 자동차 앱 라이브러리 미디어 앱에 미디어 재생 정보를 표시합니다. 이 템플릿을 사용하면 제목과 맞춤설정 가능한 작업으로 헤더를 설정할 수 있으며 미디어 정보 및 재생 컨트롤은 앱의 MediaSession 상태를 기반으로 호스트에서 채워집니다.

음악 플레이어에 기타를 연주하는 여성의 정사각형 초상화와 함께 Summer Fielding의 Sounds of Spring이 표시됩니다.

그림 1: 상단에 큐를 여는 헤더 작업이 있는 MediaPlaybackTemplate

이 코드 예에서는 사용자가 노래 대기열이 있는 화면으로 이동할 수 있는 헤더 작업을 설정하는 재생 템플릿 예를 빌드하는 방법을 보여줍니다.

val playbackTemplate = MediaPlaybackTemplate.Builder()
      .setHeader(
        Header.Builder()
          .setStartHeaderAction(Action.BACK)
          .addEndHeaderAction(
                Action.Builder()
                  .setTitle(model.context.getString(R.string.queue_button_title))
                  .setIcon(
                    CarIcon.Builder(
                        IconCompat.createWithResource(
                          model.context,
                          R.drawable.gs_queue_music_vd_theme_24,
                        ))
                      .build())
                  .setOnClickListener(showQueueScreen())
                  .build())
          .setTitle(model.context.getString(R.string.media_playback_view_title))
          .build())
      .build()

MediaPlaybackTemplate을 사용하는 경우 CarAppService에서 MediaPlaybackManager를 사용하여 a MediaSession 토큰을 등록합니다. 이렇게 하지 않으면 MediaPlaybackTemplate이 호스트로 전송될 때 오류가 표시됩니다.

import androidx.car.app.media.MediaPlaybackManager


override fun onCreateSession(sessionInfo: SessionInfo): Session {
    return object : Session() {
        

        init {
          lifecycle.addObserver(
            LifecycleEventObserver { _, event ->
              if (event == ON_CREATE) {
                val token = ... // MediaSessionCompat.Token
                (carContext.getCarService(CarContext.MEDIA_PLAYBACK_SERVICE) as MediaPlaybackManager)
                  .registerMediaPlaybackToken(token)
              }
              ...
            }
          )
        }
    }
}

.registerMediaPlaybackToken은 미디어 재생 정보 및 컨트롤을 Android Auto에 노출하는 데 필요합니다. 이는 호스트가 미디어 관련 알림을 만드는 데도 중요합니다.

표준 MediaSessionCompat.Token이 아닌 PlatformToken을 사용하는 Media3 라이브러리를 사용하는 앱의 경우 세션의 기본 플랫폼 토큰인 session.platformToken을 반환하는 MediaLibrarySession.Callback에서 맞춤 SessionCommand를 구현해야 합니다. CarAppService에서 이 맞춤 명령어를 세션으로 전송합니다. 플랫폼 토큰을 수신하면 MediaSessionCompat.Token.fromToken(platformToken)을 사용하여 변환하고 이 호환 토큰을 .registerMediaPlaybackToken()의 자동차 앱 라이브러리에 전달합니다.

템플릿을 사용하여 미디어 정리

노래 또는 앨범과 같은 탐색을 위해 미디어를 정리하려면 SectionedItemTemplate을(를) 사용하는 것이 좋습니다. 이를 통해 GridSectionRowSection을(를) 함께 사용하여 이미지 및 텍스트 항목 목록을 혼합하는 레이아웃을 만들 수 있습니다.

음악 앱 인터페이스에 최근 재생한 노래와 앨범이 표시됩니다. 여기에는 세로 행 2개와 가로 앨범 아트 초상화 3개가 포함됩니다.

그림 2: RowSection 다음에 GridSection이 포함된 SectionedItemTemplate

TabTemplate 내에서 SectionedItemTemplate 사용

앱 내에서 미디어를 분류하는 편리한 방법 중 하나는 SectionedItemTemplate 내에서 TabTemplate을 사용하는 것입니다.

val template =
      SectionedItemTemplate.Builder()...build();
val tabTemplate = 
      TabTemplate.Builder(tabCallback)
          .setTabContents(TabContents.Builder(template).build)
          .setHeaderAction(Action.APP_ICON)
          
          .build();

자동차 앱 라이브러리 1.9 구성요소 및 기능

자동차 앱 라이브러리 API 버전 1.9에서는 칩, 진행률 표시줄, 축소된 항목, 대화형 및 확장된 헤더, 스포트라이트 섹션, 배너와 같은 고유한 탐색 기능을 위한 맞춤 구성요소를 도입합니다.

음악 앱 인터페이스에 최근 재생한 노래와 앨범이 표시됩니다. 여기에는 세로 행 2개와 가로 앨범 아트 초상화 3개가 포함됩니다.

그림 3: SectionedItemTemplateChips, Condensed Items, Interactive Header, Grid Items, Minimized Control Panel이 포함됨

음악 앱 인터페이스에 최근 재생한 노래와 앨범이 표시됩니다. 여기에는 세로 행 2개와 가로 앨범 아트 초상화 3개가 포함됩니다.

그림 4: 미디어 탐색 화면 2개(다음 포함: Expanded Header, Spotlight Sections, Progress Bars)

이러한 템플릿을 사용하여 미디어 앱의 사용자 인터페이스를 디자인하는 방법에 관한 자세한 내용은 미디어 앱을 참고하세요.

미디어를 탐색할 때 사용자가 주의 산만을 최소화하면서 MediaPlaybackTemplate으로 빠르게 이동할 수 있는 것이 중요합니다.MFT-1 품질 요구사항을 충족하려면 앱에 모든 미디어 탐색 화면에서 MediaPlaybackTemplate에 액세스할 수 있는 방법이 있어야 합니다.

SectionedItemTemplate을 사용하는 경우 미디어 재생 화면으로 이동하는 작업 버튼을 추가하여 이 작업을 실행할 수 있습니다. 표준 자동차 앱 라이브러리 Action.MEDIA_PLAYBACK 작업을 사용합니다. 미디어 앱은 이 작업을 최소화된 제어판으로 표시하며, 자동차 앱 라이브러리 API 1.9 이상을 사용하는 경우 MFT-1 품질 요구사항을 충족하는 데 필요합니다. 다른 템플릿의 경우 헤더 작업이 이를 실행하는 또 다른 방법입니다.

시스템 미디어 재생 인텐트 처리

미디어 카드와 같은 미디어를 재생하는 시스템 화면에서 애플리케이션이 실행될 때 사용자를 MediaPlaybackTemplate으로 안내해야 합니다. 사용자에게 원활한 환경을 제공하려면 미디어 애플리케이션이 이 Intent Action을(를) 처리해야 합니다.

자동차 앱 라이브러리 구성요소 (CarAppActivity 또는 트램펄린 Activity)의 intent-filter에 androidx.car.app.media.action.SHOW_MEDIA_PLAYBACK 작업을 추가합니다.

onNewIntent()가 호출되도록 활동에서 launchMode singleTask 또는 singleTop을 사용하는지 확인합니다.

<activity
    android:name=".LaunchableTrampoline"
    android:exported="true"
    android:theme="@android:style/Theme.DeviceDefault.NoActionBar"
    android:launchMode="singleTask"
    android:label="@string/app_name_cal"
    android:enabled="false">

    <meta-data android:name="distractionOptimized" android:value="true" />

    <intent-filter>
        <action android:name="android.intent.action.MAIN"/>
        <action android:name="androidx.car.app.media.action.SHOW_MEDIA_PLAYBACK"/>
        <category android:name="android.intent.category.LAUNCHER"/>
    </intent-filter>
</activity>

Session 클래스에서 onNewIntent()를 재정의하여 수신 인텐트를 파싱합니다. 수신 인텐트 작업이 SHOW_MEDIA_PLAYBACK과 일치하면 사용자를 현재 재생 중인 화면으로 이동합니다.

@Override
public void onNewIntent(@NonNull Intent intent) {
    super.onNewIntent(intent);
    if (SHOW_MEDIA_PLAYBACK.equals(intent.getAction())) {
        ScreenManager screenManager = getCarContext().getCarService(ScreenManager.class);
        // Avoid redundant navigation if already on the playing screen
        if (screenManager.getTop() instanceof MyMediaPlayScreen) {
            return;
        }
        screenManager.push(MyMediaPlayScreen.createScreenFromPlaying(
                getCarContext(), mMediaSessionController));
    }
}

트램펄린 활동을 사용하는 경우 onCreate() 내에서 인텐트 작업을 확인합니다. finish()를 호출하기 전에 이 작업을 CarAppActivity 생성 인텐트에 전달합니다.

public class LaunchableTrampoline extends AppCompatActivity {
    private static final String SHOW_MEDIA_PLAYBACK = "androidx.car.app.media.action.SHOW_MEDIA_PLAYBACK";

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        Intent receivedIntent = getIntent();
        String action;

        if (SHOW_MEDIA_PLAYBACK.equals(receivedIntent.getAction())) {
            action = SHOW_MEDIA_PLAYBACK;
        } else {
            action = Intent.ACTION_MAIN;
        }

        Intent intent = new Intent(action);
        intent.setClassName(getPackageName(), "androidx.car.app.activity.CarAppActivity");
        startActivity(intent);
        finish();
    }
}