TV 입력 서비스 개발

TV 입력 서비스는 미디어 스트림 소스를 나타내며, 미디어 콘텐츠를 채널 및 프로그램과 같은 선형 방송 TV 방식으로 표시할 수 있습니다. TV 입력 서비스를 사용하면 자녀 보호 기능, 프로그램 가이드 정보, 콘텐츠 등급을 제공할 수 있습니다. TV 입력 서비스는 Android 시스템 TV 앱에서 작동합니다. 이 앱은 궁극적으로 TV에 채널 콘텐츠를 제어하고 제공합니다. 시스템 TV 앱은 기기 전용으로 개발되었으며 서드 파티 앱으로 변경할 수 없습니다. TV 입력 프레임워크 (TIF) 아키텍처와 구성요소에 관한 자세한 내용은 TV 입력 프레임워크를 참고하세요.

TIF 컴패니언 라이브러리를 사용하여 TV 입력 서비스 만들기

TIF 컴패니언 라이브러리는 일반적인 TV 입력 서비스 기능의 확장 가능한 구현을 제공하는 프레임워크입니다. 이는 OEM에서 Android 5.0 (API 수준 21)~Android 7.1 (API 수준 25)용 채널을 빌드하는 데만 사용하도록 되어 있습니다.

프로젝트 업데이트

TIF 컴패니언 라이브러리는 androidtv-sample-inputs 저장소에서 OEM이 레거시 용도로 사용할 수 있습니다. 앱에 라이브러리를 포함하는 방법의 예는 이 저장소를 참고하세요.

매니페스트에서 TV 입력 서비스 선언

앱은 시스템에서 앱에 액세스하는 데 사용하는 TvInputService 호환 서비스를 제공해야 합니다. TIF 컴패니언 라이브러리는 BaseTvInputService 클래스를 제공하며 이 클래스는 맞춤설정할 수 있는 TvInputService의 기본 구현을 제공합니다. BaseTvInputService의 서브클래스를 만들고 매니페스트에서 서브클래스를 서비스로 선언합니다.

매니페스트 선언 내에서 BIND_TV_INPUT 권한을 지정하여 서비스에서 TV 입력을 시스템에 연결할 수 있도록 합니다. 시스템 서비스가 바인딩을 실행하며 시스템 서비스에 BIND_TV_INPUT 권한이 있습니다. 시스템 TV 앱은 TvInputManager 인터페이스를 통해 TV 입력 서비스에 요청을 보냅니다.

서비스 선언에서 TvInputService를 인텐트로 실행할 작업으로 지정하는 인텐트 필터를 포함합니다. 또한 서비스 메타데이터를 별도의 XML 리소스로 선언합니다. 서비스 선언, 인텐트 필터 및 서비스 메타데이터 선언은 다음 예에 나와 있습니다.

<service android:name=".rich.RichTvInputService"
    android:label="@string/rich_input_label"
    android:permission="android.permission.BIND_TV_INPUT">
    <!-- Required filter used by the system to launch our account service. -->
    <intent-filter>
        <action android:name="android.media.tv.TvInputService" />
    </intent-filter>
    <!-- An XML file which describes this input. This provides pointers to
    the RichTvInputSetupActivity to the system/TV app. -->
    <meta-data
        android:name="android.media.tv.input"
        android:resource="@xml/richtvinputservice" />
</service>

서비스 메타데이터를 별도의 XML 파일에 정의합니다. 서비스 메타데이터 XML 파일에는 TV 입력의 초기 구성 및 채널 스캔을 설명하는 설정 인터페이스가 포함되어야 합니다. 메타데이터 파일에는 사용자가 콘텐츠를 녹화할 수 있는지 여부를 나타내는 플래그도 포함되어야 합니다. 앱에서 콘텐츠 녹화를 지원하는 방법에 관한 자세한 내용은 콘텐츠 녹화 지원을 참고하세요.

서비스 메타데이터 파일은 앱의 XML 리소스 디렉터리에 있으며 매니페스트에서 선언한 리소스의 이름과 일치해야 합니다. 이전 예의 매니페스트 항목을 사용하여 res/xml/richtvinputservice.xml에 다음 콘텐츠가 포함된 XML 파일을 만들 수 있습니다.

<?xml version="1.0" encoding="utf-8"?>
<tv-input xmlns:android="http://schemas.android.com/apk/res/android"
  android:canRecord="true"
  android:setupActivity="com.example.android.sampletvinput.rich.RichTvInputSetupActivity" />

채널 정의 및 설정 활동 만들기

TV 입력 서비스는 사용자가 시스템 TV 앱을 통해 액세스하는 채널을 하나 이상 정의해야 합니다. 시스템 데이터베이스에 채널을 등록하고 시스템에서 앱의 채널을 찾을 수 없을 때 호출하는 설정 활동을 제공해야 합니다.

먼저 앱이 시스템 전자 프로그래밍 가이드 (EPG)에서 읽고 쓸 수 있도록 사용 설정합니다. EPG의 데이터에는 사용자가 사용할 수 있는 채널과 프로그램이 포함됩니다. 앱이 이러한 작업을 실행하고 기기가 다시 시작된 후 EPG와 동기화되도록 하려면 앱 매니페스트에 다음 요소를 추가합니다.

<uses-permission android:name="com.android.providers.tv.permission.WRITE_EPG_DATA" />
<uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED "/>

앱이 Google Play 스토어에 Android TV에서 콘텐츠 채널을 제공하는 앱으로 표시되도록 다음 요소를 추가합니다.

<uses-feature
    android:name="android.software.live_tv"
    android:required="true" />

그런 다음 EpgSyncJobService 클래스를 확장하는 클래스를 만듭니다. 이 추상 클래스를 사용하면 시스템 데이터베이스에서 채널을 만들고 업데이트하는 작업 서비스를 쉽게 만들 수 있습니다.

서브클래스에서 getChannels()에 전체 채널 목록을 만들고 반환합니다. XMLTV 파일에서 채널을 가져온 경우 XmlTvParser 클래스를 사용합니다. 그 외의 경우에는 Channel.Builder 클래스를 사용하여 프로그래매틱 방식으로 채널을 생성합니다.

각 채널에서 시스템은 지정된 시간 내에 채널에서 볼 수 있는 프로그램 목록이 필요할 때 getProgramsForChannel()를 호출합니다. 채널의 Program 객체 목록을 반환합니다. XmlTvParser 클래스를 사용하여 XMLTV 파일에서 프로그램을 가져오거나 Program.Builder 클래스를 사용하여 프로그래매틱 방식으로 생성합니다.

Program 객체의 경우 InternalProviderData 객체를 사용하여 프로그램의 동영상 유형과 같은 프로그램 정보를 설정합니다. 채널에서 루프를 반복할 프로그램 수가 제한되어 있다면 프로그램에 관한 정보를 설정할 때 InternalProviderData.setRepeatable() 메서드를 true 값과 함께 사용합니다.

작업 서비스를 구현한 후 다음과 같이 앱 매니페스트에 추가하세요.

<service
    android:name=".sync.SampleJobService"
    android:permission="android.permission.BIND_JOB_SERVICE"
    android:exported="true" />

마지막으로 설정 활동을 만드세요. 설정 활동은 채널과 프로그램 데이터를 동기화하는 방법을 제공해야 합니다. 이렇게 하는 한 가지 방법은 사용자가 활동의 UI를 통해 실행하는 것입니다. 활동이 시작될 때 앱에서 자동으로 실행하도록 할 수도 있습니다. 설정 활동에서 채널과 프로그램 정보를 동기화해야 하는 경우 앱은 작업 서비스를 시작해야 합니다.

Kotlin

val inputId = getActivity().intent.getStringExtra(TvInputInfo.EXTRA_INPUT_ID)
EpgSyncJobService.cancelAllSyncRequests(getActivity())
EpgSyncJobService.requestImmediateSync(
        getActivity(),
        inputId,
        ComponentName(getActivity(), SampleJobService::class.java)
)

Java

String inputId = getActivity().getIntent().getStringExtra(TvInputInfo.EXTRA_INPUT_ID);
EpgSyncJobService.cancelAllSyncRequests(getActivity());
EpgSyncJobService.requestImmediateSync(getActivity(), inputId,
        new ComponentName(getActivity(), SampleJobService.class));

requestImmediateSync() 메서드를 사용하여 작업 서비스를 동기화합니다. 사용자는 동기화가 완료될 때까지 기다려야 하므로 요청 기간을 비교적 짧게 유지해야 합니다.

setUpPeriodicSync() 메서드를 사용하여 작업 서비스가 백그라운드에서 채널 및 프로그램 데이터를 주기적으로 동기화하도록 합니다.

Kotlin

EpgSyncJobService.setUpPeriodicSync(
        context,
        inputId,
        ComponentName(context, SampleJobService::class.java)
)

Java

EpgSyncJobService.setUpPeriodicSync(context, inputId,
        new ComponentName(context, SampleJobService.class));

TIF 컴패니언 라이브러리는 채널 데이터의 동기화 기간을 밀리초 단위로 지정할 수 있는 requestImmediateSync()의 오버로드된 메서드를 추가로 제공합니다. 기본 메서드는 1시간 분량의 채널 데이터를 동기화합니다

TIF 컴패니언 라이브러리는 채널 데이터의 동기화 기간과 주기적 동기화 발생 빈도를 지정할 수 있는 setUpPeriodicSync()의 오버로드된 메서드도 추가로 제공합니다. 기본 메서드는 12시간마다 48시간의 채널 데이터를 동기화합니다.

채널 데이터 및 EPG에 관한 자세한 내용은 채널 데이터 작업을 참고하세요.

미세 조정 요청 및 미디어 재생 처리

사용자가 특정 채널을 선택하면 시스템 TV 앱은 앱에서 만든 Session을 사용하여 요청된 채널에 맞추고 콘텐츠를 재생합니다. TIF 컴패니언 라이브러리는 시스템의 채널 및 세션 호출을 처리하기 위해 확장할 수 있는 여러 클래스를 제공합니다.

BaseTvInputService 서브클래스는 미세 조정 요청을 처리하는 세션을 만듭니다. onCreateSession() 메서드를 재정의하고 BaseTvInputService.Session 클래스에서 확장된 세션을 만든 다음 새 세션으로 super.sessionCreated()를 호출합니다. 다음 예에서 onCreateSession()BaseTvInputService.Session를 확장하는 RichTvInputSessionImpl 객체를 반환합니다.

Kotlin

override fun onCreateSession(inputId: String): Session =
        RichTvInputSessionImpl(this, inputId).apply {
            setOverlayViewEnabled(true)
        }

Java

@Override
public final Session onCreateSession(String inputId) {
    RichTvInputSessionImpl session = new RichTvInputSessionImpl(this, inputId);
    session.setOverlayViewEnabled(true);
    return session;
}

사용자가 시스템 TV 앱을 사용하여 채널 중 하나를 보기 시작하면 시스템은 세션의 onPlayChannel() 메서드를 호출합니다. 프로그램 재생을 시작하기 전에 특수 채널 초기화를 실행해야 하는 경우 이 메서드를 재정의합니다.

그러면 시스템은 현재 예약된 프로그램을 가져오고 세션의 onPlayProgram() 메서드를 호출하여 프로그램 정보를 지정하고 시작 시간을 밀리초 단위로 지정합니다. 프로그램 재생을 시작하려면 TvPlayer 인터페이스를 사용합니다.

미디어 플레이어 코드는 특정 재생 이벤트를 처리하기 위해 TvPlayer를 구현해야 합니다. TvPlayer 클래스는 BaseTvInputService 구현을 더 복잡하게 하지 않고 타임 시프팅 컨트롤과 같은 기능을 처리합니다.

세션의 getTvPlayer() 메서드에서 TvPlayer를 구현하는 미디어 플레이어를 반환합니다. TV 입력 서비스 샘플 앱은 ExoPlayer를 사용하는 미디어 플레이어를 구현합니다.

TV 입력 프레임워크를 사용하여 TV 입력 서비스 만들기

TV 입력 서비스가 TIF 컴패니언 라이브러리를 사용할 수 없다면 다음 구성요소를 구현해야 합니다.

  • TvInputService는 TV 입력에 장기 실행 및 백그라운드 가용성을 제공합니다.
  • TvInputService.Session: TV 입력 상태를 유지하고 호스팅 앱과 통신합니다.
  • TvContract은 TV 입력에 사용할 수 있는 채널 및 프로그램을 설명합니다.
  • TvContract.Channels - TV 채널에 관한 정보를 나타냅니다.
  • TvContract.Programs은 프로그램 제목 및 시작 시간과 같은 데이터를 사용하여 TV 프로그램을 설명합니다.
  • TvTrackInfo - 오디오, 동영상 또는 자막 트랙을 나타냅니다.
  • TvContentRating는 콘텐츠 등급을 설명하고 맞춤 콘텐츠 등급 체계를 허용합니다.
  • TvInputManager: 시스템 TV 앱에 API를 제공하고 TV 입력 및 앱과의 상호작용을 관리합니다.

또한 다음을 실행해야 합니다.

  1. 매니페스트에서 TV 입력 서비스 선언에 설명된 대로 매니페스트에서 TV 입력 서비스를 선언합니다.
  2. 서비스 메타데이터 파일을 만듭니다.
  3. 채널과 프로그램 정보를 만들고 등록합니다.
  4. 설정 활동을 만듭니다.

TV 입력 서비스 정의

서비스의 TvInputService 클래스를 확장합니다. TvInputService 구현은 바인드된 서비스이며, 여기서 시스템 서비스는 바인딩되는 클라이언트입니다. 구현해야 하는 서비스 수명 주기 메서드는 그림 1에 나와 있습니다.

onCreate() 메서드는 시스템 기반 작업을 처리하기 위해 UI 스레드와 별도로 프로세스 스레드를 제공하는 HandlerThread를 초기화하고 시작합니다. 다음 예에서 onCreate() 메서드는 CaptioningManager를 초기화하고 ACTION_BLOCKED_RATINGS_CHANGEDACTION_PARENTAL_CONTROLS_ENABLED_CHANGED 작업을 처리할 준비를 합니다. 이러한 작업은 사용자가 자녀 보호 기능 설정을 변경할 때 및 차단된 등급 목록이 변경되었을 때 실행된 시스템 인텐트를 설명합니다.

Kotlin

override fun onCreate() {
    super.onCreate()
    handlerThread = HandlerThread(javaClass.simpleName).apply {
        start()
    }
    dbHandler = Handler(handlerThread.looper)
    handler = Handler()
    captioningManager = getSystemService(Context.CAPTIONING_SERVICE) as CaptioningManager

    setTheme(android.R.style.Theme_Holo_Light_NoActionBar)

    sessions = mutableListOf<BaseTvInputSessionImpl>()
    val intentFilter = IntentFilter().apply {
        addAction(TvInputManager.ACTION_BLOCKED_RATINGS_CHANGED)
        addAction(TvInputManager.ACTION_PARENTAL_CONTROLS_ENABLED_CHANGED)
    }
    registerReceiver(broadcastReceiver, intentFilter)
}

Java

@Override
public void onCreate() {
    super.onCreate();
    handlerThread = new HandlerThread(getClass()
      .getSimpleName());
    handlerThread.start();
    dbHandler = new Handler(handlerThread.getLooper());
    handler = new Handler();
    captioningManager = (CaptioningManager)
      getSystemService(Context.CAPTIONING_SERVICE);

    setTheme(android.R.style.Theme_Holo_Light_NoActionBar);

    sessions = new ArrayList<BaseTvInputSessionImpl>();
    IntentFilter intentFilter = new IntentFilter();
    intentFilter.addAction(TvInputManager
      .ACTION_BLOCKED_RATINGS_CHANGED);
    intentFilter.addAction(TvInputManager
      .ACTION_PARENTAL_CONTROLS_ENABLED_CHANGED);
    registerReceiver(broadcastReceiver, intentFilter);
}

그림 1.TvInputService 수명 주기

차단된 콘텐츠를 사용하고 자녀 보호 기능을 제공하는 방법에 관한 자세한 내용은 콘텐츠 제어를 참고하세요. TV 입력 서비스에서 처리할 수 있는 더 많은 시스템 기반 작업은 TvInputManager를 참고하세요.

TvInputServiceHandler.Callback를 구현하여 플레이어 상태 변경을 처리하는 TvInputService.Session를 만듭니다. onSetSurface()를 사용하면 TvInputService.Session는 동영상 콘텐츠로 Surface를 설정합니다. Surface를 사용하여 동영상을 렌더링하는 방법에 관한 자세한 내용은 플레이어를 표면과 통합을 참고하세요.

TvInputService.Session는 사용자가 채널을 선택할 때 onTune() 이벤트를 처리하고 콘텐츠 및 콘텐츠 메타데이터의 변경사항을 시스템 TV 앱에 알립니다. 이러한 notify() 메서드는 이 교육 과정의 뒷부분에서 콘텐츠 제어트랙 선택 처리에 설명되어 있습니다.

설정 활동 정의

시스템 TV 앱에서는 TV 입력에 정의된 설정 활동을 사용합니다. 설정 활동은 필수 항목이며 시스템 데이터베이스에 채널 레코드를 하나 이상 제공해야 합니다. 시스템 TV 앱은 TV 입력의 채널을 찾을 수 없을 때 설정 활동을 호출합니다.

설정 활동은 다음 과정 채널 데이터 만들기 및 업데이트에 설명된 대로 TV 입력을 통해 제공된 채널을 시스템 TV 앱에 설명합니다.

추가 자료