웹 앱에서 Cast 지원 사용

1. 개요

Google Cast 로고

이 Codelab에서는 기존 웹 동영상 앱을 수정하여 Google Cast 지원 기기에서 콘텐츠를 전송하는 방법을 알아봅니다.

Google Cast란 무엇인가요?

사용자는 Google Cast를 사용하여 휴대기기의 콘텐츠를 TV로 전송할 수 있습니다. 그런 다음, 휴대기기를 리모컨으로 사용해 TV에서 재생 중인 미디어를 제어할 수 있습니다.

Google Cast SDK를 사용하면 앱을 확장하여 TV 또는 사운드 시스템을 제어할 수 있습니다. 다시 말해, Google Cast 디자인 체크리스트에 따라 필요한 UI 구성요소를 추가하는 것이 가능합니다.

Google Cast 디자인 체크리스트는 지원되는 모든 플랫폼에서 Cast 사용자 환경을 간단하고 예측 가능하게 만들기 위해 제공됩니다.

무엇을 빌드하게 되나요?

이 Codelab을 완료하면 Google Cast 기기로 동영상을 전송할 수 있는 Chrome 웹 동영상 앱이 생성됩니다.

학습할 내용

  • 샘플 동영상 앱에 Google Cast SDK를 추가하는 방법
  • Google Cast 기기를 선택할 수 있는 전송 버튼을 추가하는 방법
  • Cast 기기에 연결하고 미디어 수신기를 실행하는 방법
  • 동영상을 전송하는 방법
  • Cast Connect 통합 방법

필요한 항목

  • 최신 Chrome 브라우저
  • HTTPS 호스팅 서비스(예: Firebase 호스팅 또는 ngrok)
  • Chromecast 또는 Android TV와 같이 인터넷에 액세스할 수 있도록 설정된 Google Cast 기기
  • HDMI 입력 단자가 있는 TV 또는 모니터
  • Cast Connect 통합을 테스트하려면 Chromecast with Google TV가 필요하지만 Codelab의 나머지 부분에서는 선택사항입니다. 계정이 없는 경우 이 튜토리얼의 끝부분에 있는 Cast Connect 지원 추가 단계를 건너뛰어도 됩니다.

환경

  • 웹 개발에 관한 사전 지식이 있어야 합니다.
  • 또한 TV 시청에 관한 사전 지식도 필요합니다. :)

본 가이드를 어떻게 사용하실 계획인가요?

읽기만 할 계획입니다 읽은 다음 연습 활동을 완료할 계획입니다

본인의 웹 앱 빌드 경험을 평가해 주세요.

초급 중급 고급

TV 시청 관련 경험을 평가해 주세요.

초급 중급 고급

2. 샘플 코드 가져오기

모든 샘플 코드를 컴퓨터에 다운로드할 수 있습니다.

그런 다음 다운로드한 ZIP 파일의 압축을 풉니다.

3. 샘플 앱 실행

Chrome 로고

먼저 완성된 샘플 앱이 어떤 모습인지 살펴보겠습니다. 기본 동영상 플레이어로 사용되는 앱입니다. 사용자가 목록에서 동영상을 선택한 다음 기기에서 로컬로 재생하거나 Google Cast 기기로 전송할 수 있습니다.

완료된 파일을 사용하려면 해당 파일을 호스팅해야 합니다.

사용할 수 있는 서버가 없으면 Firebase 호스팅 또는 ngrok를 사용할 수 있습니다.

서버 실행

원하는 서비스를 설정했다면 app-done로 이동하여 서버를 시작합니다.

브라우저에서 호스팅한 샘플의 https URL로 이동합니다.

  1. 동영상 앱이 표시됩니다.
  2. 전송 버튼을 클릭하고 Google Cast 기기를 선택합니다.
  3. 동영상을 선택하고 재생 버튼을 클릭합니다.
  4. Google Cast 기기에서 동영상이 재생되기 시작합니다.

Cast 기기에서 재생 중인 동영상 이미지

동영상 요소에서 일시중지 버튼을 클릭하여 수신기에서 동영상을 일시중지합니다. 동영상 요소에서 재생 버튼을 클릭하여 동영상을 다시 계속 재생합니다.

Google Cast 기기로의 전송을 중지하려면 전송 버튼을 클릭합니다.

계속하기 전에 서버를 중지합니다.

4. 시작 프로젝트 준비

Cast 기기에서 재생 중인 동영상 이미지

다운로드한 시작 앱에 Google Cast 지원 기능을 추가해야 합니다. 다음은 이 Codelab에서 사용할 Google Cast 용어입니다.

  • 발신기 앱은 휴대기기 또는 노트북에서 실행됩니다.
  • 수신기 앱은 Google Cast 기기에서 실행됩니다.

이제 선호하는 텍스트 편집기를 사용하여 시작 프로젝트 위에 빌드할 준비가 되었습니다.

  1. 샘플 코드 다운로드에서 폴더 아이콘app-start 디렉터리를 선택합니다.
  2. 서버를 사용하여 앱을 실행하고 UI를 탐색합니다.

이 Codelab을 진행하면서 서비스에 따라 서버에서 샘플을 재호스팅해야 합니다.

앱 디자인

앱이 원격 웹 서버에서 동영상 목록을 가져오고 사용자가 둘러볼 수 있도록 목록을 제공합니다. 사용자는 동영상을 선택하여 세부정보를 보거나 휴대기기에서 로컬로 동영상을 재생할 수 있습니다.

앱은 index.html에 정의된 기본 뷰 하나와 기본 컨트롤러 CastVideos.js.로 구성됩니다.

index.html

이 html 파일은 웹 앱의 UI를 거의 모두 선언합니다.

뷰 섹션에는 동영상 요소가 포함된 div#main_video가 있습니다. 동영상 div와 관련하여 동영상 요소의 모든 컨트롤을 정의하는 div#media_control가 있습니다. 그 아래에는 뷰에 동영상의 세부정보를 표시하는 media_info가 있습니다. 마지막으로 carousel div는 div의 동영상 목록을 표시합니다.

index.html 파일은 Cast SDK를 부트스트랩하고 CastVideos 함수에 로드하도록 지시합니다.

이러한 요소를 채우는 대부분의 콘텐츠는 CastVideos.js에서 정의, 삽입 및 제어됩니다. 그럼, 이 내용을 살펴보겠습니다.

CastVideos.js

이 스크립트는 Cast 동영상 웹 앱의 모든 로직을 관리합니다. CastVideos.js에 정의된 동영상 및 관련 메타데이터의 목록은 mediaJSON라는 객체에 포함되어 있습니다.

동영상을 로컬과 원격으로 관리하고 재생하는 몇 가지 주요 섹션이 있습니다. 전반적으로 이것은 상당히 간단한 웹 애플리케이션입니다.

CastPlayer는 전체 앱을 관리하는 기본 클래스로, 플레이어를 설정하고 미디어를 선택하며 미디어를 재생하기 위해 이벤트를 PlayerHandler에 바인딩합니다. CastPlayer.prototype.initializeCastPlayer는 모든 Cast 기능을 설정하는 메서드입니다. CastPlayer.prototype.switchPlayer는 로컬 플레이어와 원격 플레이어 간에 상태를 전환합니다. CastPlayer.prototype.setupLocalPlayerCastPlayer.prototype.setupRemotePlayer는 로컬 플레이어와 원격 플레이어를 초기화합니다.

PlayerHandler는 미디어 재생을 관리하는 클래스입니다. 미디어 및 재생을 세부적으로 관리하는 여러 가지 메서드가 있습니다.

자주 묻는 질문(FAQ)

5. 전송 버튼 추가

Cast 지원 앱 이미지

Cast 지원 애플리케이션에서 동영상 요소에 전송 버튼을 표시합니다. 전송 버튼을 클릭하면 사용자가 선택할 수 있는 Cast 기기 목록이 표시됩니다. 사용자가 발신기 기기에서 로컬로 콘텐츠를 재생 중인 경우 Cast 기기를 선택하면 Cast 기기에서 재생이 시작되거나 재개됩니다. 사용자는 Cast 세션 중 언제든지 전송 버튼을 클릭하여 애플리케이션의 Cast 기기 전송을 중지할 수 있습니다. Google Cast 디자인 체크리스트에 설명된 대로 사용자는 애플리케이션의 모든 화면에서 Cast 기기에 연결하거나 연결 해제할 수 있어야 합니다.

구성

시작 프로젝트에는 완료된 샘플 앱과 동일한 종속 항목 및 설정이 필요하지만 이번에는 app-start의 콘텐츠를 호스팅합니다.

브라우저에서 호스팅한 샘플의 https URL로 이동합니다.

변경할 때 서비스에 따라 서버에서 샘플을 재호스팅해야 합니다.

초기화

Cast 프레임워크에는 프레임워크의 모든 활동을 조정하는 전역 싱글톤 객체 CastContext가 있습니다. 이 객체는 애플리케이션의 수명 주기 초기에 초기화해야 합니다. 일반적으로 window['__onGCastApiAvailable']에 할당된 콜백에서 호출되며, Cast SDK가 로드된 후 호출되어 사용할 수 있습니다. 이 경우 CastContext는 앞서 언급한 콜백에서 호출되는 CastPlayer.prototype.initializeCastPlayer에서 호출됩니다.

CastContext를 초기화할 때 options JSON 객체를 제공해야 합니다. 이 클래스에는 프레임워크 동작에 영향을 미치는 옵션이 있습니다. 그중 가장 중요한 것은 수신기 애플리케이션 ID입니다. 지정된 앱을 실행할 수 있는 기기만 표시하고 Cast 세션이 시작될 때 수신기 애플리케이션을 실행하도록 사용 가능한 Cast 기기 목록을 필터링하는 데 사용됩니다.

자체 Cast 지원 앱을 개발하는 경우 Cast 개발자로 등록한 다음 앱의 애플리케이션 ID를 받아야 합니다. 이 Codelab에서는 샘플 앱 ID를 사용합니다.

다음 코드를 body 섹션 끝의 index.html에 추가합니다.

<script type="text/javascript" src="https://www.gstatic.com/cv/js/sender/v1/cast_sender.js?loadCastFramework=1"></script>

index.html에 다음 코드를 추가하여 CastVideos 앱을 초기화하고 CastContext도 초기화합니다.

<script src="CastVideos.js"></script>
<script type="text/javascript">
var castPlayer = new CastPlayer();
window['__onGCastApiAvailable'] = function(isAvailable) {
  if (isAvailable) {
    castPlayer.initializeCastPlayer();
  }
};
</script>

이제 CastVideos.js에 방금 index.html에서 호출한 메서드에 해당하는 새 메서드를 추가해야 합니다. CastContext에서 옵션을 설정하고 새 RemotePlayerRemotePlayerControllers를 초기화하는 initializeCastPlayer라는 새 메서드를 추가해 보겠습니다.

/**
 * This method sets up the CastContext, and a few other members
 * that are necessary to play and control videos on a Cast
 * device.
 */
CastPlayer.prototype.initializeCastPlayer = function() {

    var options = {};

    // Set the receiver application ID to your own (created in
    // the Google Cast Developer Console), or optionally
    // use the chrome.cast.media.DEFAULT_MEDIA_RECEIVER_APP_ID
    options.receiverApplicationId = 'C0868879';

    // Auto join policy can be one of the following three:
    // ORIGIN_SCOPED - Auto connect from same appId and page origin
    // TAB_AND_ORIGIN_SCOPED - Auto connect from same appId, page origin, and tab
    // PAGE_SCOPED - No auto connect
    options.autoJoinPolicy = chrome.cast.AutoJoinPolicy.ORIGIN_SCOPED;

    cast.framework.CastContext.getInstance().setOptions(options);

    this.remotePlayer = new cast.framework.RemotePlayer();
    this.remotePlayerController = new cast.framework.RemotePlayerController(this.remotePlayer);
    this.remotePlayerController.addEventListener(
        cast.framework.RemotePlayerEventType.IS_CONNECTED_CHANGED,
        this.switchPlayer.bind(this)
    );
};

마지막으로 RemotePlayerRemotePlayerController에 사용할 변수를 만들어야 합니다.

var CastPlayer = function() {
  //...
  /* Cast player variables */
  /** @type {cast.framework.RemotePlayer} */
  this.remotePlayer = null;
  /** @type {cast.framework.RemotePlayerController} */
  this.remotePlayerController = null;
  //...
};

전송 버튼

이제 CastContext가 초기화되었으므로 전송 버튼을 추가하여 사용자가 Cast 기기를 선택할 수 있도록 해야 합니다. Cast SDK는 ID가 "castbutton"google-cast-launcher라는 전송 버튼 구성요소를 제공합니다. media_control 섹션에 button를 추가하기만 하면 애플리케이션의 동영상 요소에 이 구성요소를 추가할 수 있습니다.

버튼 요소는 다음과 같이 표시됩니다.

<google-cast-launcher id="castbutton"></google-cast-launcher>

다음 코드를 media_control 섹션의 index.html에 추가합니다.

<div id="media_control">
  <div id="play"></div>
  <div id="pause"></div>
  <div id="progress_bg"></div>
  <div id="progress"></div>
  <div id="progress_indicator"></div>
  <div id="fullscreen_expand"></div>
  <div id="fullscreen_collapse"></div>
  <google-cast-launcher id="castbutton"></google-cast-launcher>
  <div id="audio_bg"></div>
  <div id="audio_bg_track"></div>
  <div id="audio_indicator"></div>
  <div id="audio_bg_level"></div>
  <div id="audio_on"></div>
  <div id="audio_off"></div>
  <div id="duration">00:00:00</div>
</div>

이제 Chrome 브라우저에서 페이지를 새로고침합니다. 동영상 요소에 전송 버튼이 표시되며 이 버튼을 클릭하면 로컬 네트워크에 Cast 기기가 표시됩니다. 기기 검색은 Chrome 브라우저에서 자동으로 관리됩니다. Cast 기기를 선택하면 샘플 수신기 앱이 Cast 기기에 로드됩니다.

미디어 재생과 관련된 지원이 연결되지 않았으므로 아직은 Cast 기기에서 동영상을 재생하실 수 없습니다. 전송을 중지하려면 전송 버튼을 클릭하세요.

6. 동영상 콘텐츠 전송

Cast 기기 선택 메뉴가 표시된 Cast 지원 앱의 이미지

Cast 기기에서 원격으로 동영상을 재생할 수 있도록 샘플 앱을 확장하겠습니다. 이를 처리하려면 Cast 프레임워크에서 생성된 다양한 이벤트를 수신 대기해야 합니다.

미디어 전송

Cast 기기에서 미디어를 재생하려면 상위 수준에서 다음을 실행해야 합니다.

  1. Cast SDK에서 미디어 항목을 모델링하는 MediaInfo JSON 객체를 만듭니다.
  2. 사용자가 Cast 기기에 연결하여 수신기 애플리케이션을 실행합니다.
  3. MediaInfo 객체를 수신기에 로드하고 콘텐츠를 재생합니다.
  4. 미디어 상태를 추적합니다.
  5. 사용자 상호작용에 따라 재생 명령어를 수신기로 전송합니다.

1단계는 한 객체를 다른 객체에 매핑하는 것과 같습니다. MediaInfo은 Cast SDK가 이해하는 것이고 mediaJSON는 앱의 미디어 항목 캡슐화입니다. mediaJSONMediaInfo에 쉽게 매핑할 수 있습니다. 이전 섹션에서 이미 2단계를 완료했습니다. 3단계는 Cast SDK로 쉽게 할 수 있습니다.

CastPlayer 샘플 앱은 이미 switchPlayer 메서드에서 로컬 재생과 원격 재생을 구분합니다.

if (cast && cast.framework) {
  if (this.remotePlayer.isConnected) {
    //...

이 Codelab에서 모든 샘플 플레이어 로직의 작동 방식을 정확히 이해하는 것은 중요하지 않습니다. 하지만 로컬 재생과 원격 재생을 모두 인식하도록 앱의 미디어 플레이어를 수정해야 한다는 점을 이해하는 것이 중요합니다.

현재 로컬 플레이어는 전송 상태에 관한 정보를 모르므로 항상 로컬 재생 상태입니다. Cast 프레임워크에서 발생하는 상태 전환에 따라 UI를 업데이트해야 합니다. 예를 들어 전송을 시작하면 로컬 재생을 중지하고 일부 컨트롤을 사용 중지해야 합니다. 마찬가지로, 이 뷰 컨트롤러에서 전송을 중지하면 로컬 재생으로 전환해야 합니다. 이를 처리하려면 Cast 프레임워크에서 생성된 다양한 이벤트를 수신 대기해야 합니다.

전송 세션 관리

Cast 프레임워크의 경우 전송 세션에 기기 연결, 실행 (또는 기존 세션 참여), 수신기 애플리케이션 연결, 필요한 경우 미디어 제어 채널 초기화 단계가 결합되어 있습니다. 미디어 제어 채널은 Cast 프레임워크가 수신기에서 미디어 재생 관련 메시지를 주고받는 방법입니다.

사용자가 전송 버튼에서 기기를 선택하면 전송 세션이 자동으로 시작되고 사용자가 연결을 해제하면 자동으로 중지됩니다. 네트워킹 문제로 인해 수신기 세션에 다시 연결하는 경우도 Cast 프레임워크에서 자동으로 처리합니다.

전송 세션은 cast.framework.CastContext.getInstance().getCurrentSession()를 통해 액세스할 수 있는 CastSession에 의해 관리됩니다. EventListener 콜백은 생성, 정지, 재개, 종료와 같은 세션 이벤트를 모니터링하는 데 사용할 수 있습니다.

현재 애플리케이션에서는 모든 세션 및 상태 관리가 setupRemotePlayer 메서드에서 처리됩니다. CastVideos.js에 다음 코드를 추가하여 앱에서 구성 작업을 시작해 보겠습니다.

/**
 * Set the PlayerHandler target to use the remote player
 */
CastPlayer.prototype.setupRemotePlayer = function () {
    var castSession = cast.framework.CastContext.getInstance().getCurrentSession();

    this.playerHandler.setTarget(playerTarget);

    // Setup remote player volume right on setup
    // The remote player may have had a volume set from previous playback
    if (this.remotePlayer.isMuted) {
        this.playerHandler.mute();
    }
    var currentVolume = this.remotePlayer.volumeLevel * FULL_VOLUME_HEIGHT;
    var p = document.getElementById('audio_bg_level');
    p.style.height = currentVolume + 'px';
    p.style.marginTop = -currentVolume + 'px';

    this.hideFullscreenButton();

    this.playerHandler.play();
};

여전히 콜백의 모든 이벤트를 바인딩하고 수신되는 모든 이벤트를 처리해야 합니다. 이 작업은 매우 간단하므로 지금 처리해 보겠습니다.

/**
 * Set the PlayerHandler target to use the remote player
 */
CastPlayer.prototype.setupRemotePlayer = function () {
    var castSession = cast.framework.CastContext.getInstance().getCurrentSession();

    // Add event listeners for player changes which may occur outside sender app
    this.remotePlayerController.addEventListener(
        cast.framework.RemotePlayerEventType.IS_PAUSED_CHANGED,
        function() {
            if (this.remotePlayer.isPaused) {
                this.playerHandler.pause();
            } else {
                this.playerHandler.play();
            }
        }.bind(this)
    );

    this.remotePlayerController.addEventListener(
        cast.framework.RemotePlayerEventType.IS_MUTED_CHANGED,
        function() {
            if (this.remotePlayer.isMuted) {
                this.playerHandler.mute();
            } else {
                this.playerHandler.unMute();
            }
        }.bind(this)
    );

    this.remotePlayerController.addEventListener(
        cast.framework.RemotePlayerEventType.VOLUME_LEVEL_CHANGED,
        function() {
            var newVolume = this.remotePlayer.volumeLevel * FULL_VOLUME_HEIGHT;
            var p = document.getElementById('audio_bg_level');
            p.style.height = newVolume + 'px';
            p.style.marginTop = -newVolume + 'px';
        }.bind(this)
    );

    // This object will implement PlayerHandler callbacks with
    // remotePlayerController, and makes necessary UI updates specific
    // to remote playback
    var playerTarget = {};

    playerTarget.play = function () {
        if (this.remotePlayer.isPaused) {
            this.remotePlayerController.playOrPause();
        }

        var vi = document.getElementById('video_image');
        vi.style.display = 'block';
        var localPlayer = document.getElementById('video_element');
        localPlayer.style.display = 'none';
    }.bind(this);

    playerTarget.pause = function () {
        if (!this.remotePlayer.isPaused) {
            this.remotePlayerController.playOrPause();
        }
    }.bind(this);

    playerTarget.stop = function () {
         this.remotePlayerController.stop();
    }.bind(this);

    playerTarget.getCurrentMediaTime = function() {
        return this.remotePlayer.currentTime;
    }.bind(this);

    playerTarget.getMediaDuration = function() {
        return this.remotePlayer.duration;
    }.bind(this);

    playerTarget.updateDisplayMessage = function () {
        document.getElementById('playerstate').style.display = 'block';
        document.getElementById('playerstatebg').style.display = 'block';
        document.getElementById('video_image_overlay').style.display = 'block';
        document.getElementById('playerstate').innerHTML =
            this.mediaContents[ this.currentMediaIndex]['title'] + ' ' +
            this.playerState + ' on ' + castSession.getCastDevice().friendlyName;
    }.bind(this);

    playerTarget.setVolume = function (volumeSliderPosition) {
        // Add resistance to avoid loud volume
        var currentVolume = this.remotePlayer.volumeLevel;
        var p = document.getElementById('audio_bg_level');
        if (volumeSliderPosition < FULL_VOLUME_HEIGHT) {
            var vScale =  this.currentVolume * FULL_VOLUME_HEIGHT;
            if (volumeSliderPosition > vScale) {
                volumeSliderPosition = vScale + (pos - vScale) / 2;
            }
            p.style.height = volumeSliderPosition + 'px';
            p.style.marginTop = -volumeSliderPosition + 'px';
            currentVolume = volumeSliderPosition / FULL_VOLUME_HEIGHT;
        } else {
            currentVolume = 1;
        }
        this.remotePlayer.volumeLevel = currentVolume;
        this.remotePlayerController.setVolumeLevel();
    }.bind(this);

    playerTarget.mute = function () {
        if (!this.remotePlayer.isMuted) {
            this.remotePlayerController.muteOrUnmute();
        }
    }.bind(this);

    playerTarget.unMute = function () {
        if (this.remotePlayer.isMuted) {
            this.remotePlayerController.muteOrUnmute();
        }
    }.bind(this);

    playerTarget.isMuted = function() {
        return this.remotePlayer.isMuted;
    }.bind(this);

    playerTarget.seekTo = function (time) {
        this.remotePlayer.currentTime = time;
        this.remotePlayerController.seek();
    }.bind(this);

    this.playerHandler.setTarget(playerTarget);

    // Setup remote player volume right on setup
    // The remote player may have had a volume set from previous playback
    if (this.remotePlayer.isMuted) {
        this.playerHandler.mute();
    }
    var currentVolume = this.remotePlayer.volumeLevel * FULL_VOLUME_HEIGHT;
    var p = document.getElementById('audio_bg_level');
    p.style.height = currentVolume + 'px';
    p.style.marginTop = -currentVolume + 'px';

    this.hideFullscreenButton();

    this.playerHandler.play();
};

미디어 로드

Cast SDK에서 RemotePlayerRemotePlayerController는 수신기에서 원격 미디어 재생을 편리하게 관리할 수 있는 일련의 API를 제공합니다. 미디어 재생을 지원하는 CastSession의 경우 RemotePlayerRemotePlayerController 인스턴스가 SDK에 의해 자동으로 생성됩니다. 앞서 설명한 것처럼 cast.framework.RemotePlayercast.framework.RemotePlayerController 인스턴스를 각각 만들어 이러한 인스턴스에 액세스할 수 있습니다.

다음으로 SDK가 요청을 처리하고 전달할 MediaInfo 객체를 빌드하여 현재 선택된 동영상을 수신기에 로드해야 합니다. setupRemotePlayer에 다음 코드를 추가하면 됩니다.

/**
 * Set the PlayerHandler target to use the remote player
 */
CastPlayer.prototype.setupRemotePlayer = function () {
    //...

    playerTarget.load = function (mediaIndex) {
        console.log('Loading...' + this.mediaContents[mediaIndex]['title']);
        var mediaInfo = new chrome.cast.media.MediaInfo(
            this.mediaContents[mediaIndex]['sources'][0], 'video/mp4');

        mediaInfo.metadata = new chrome.cast.media.GenericMediaMetadata();
        mediaInfo.metadata.metadataType = chrome.cast.media.MetadataType.GENERIC;
        mediaInfo.metadata.title = this.mediaContents[mediaIndex]['title'];
        mediaInfo.metadata.images = [
            {'url': MEDIA_SOURCE_ROOT + this.mediaContents[mediaIndex]['thumb']}];

        var request = new chrome.cast.media.LoadRequest(mediaInfo);
        castSession.loadMedia(request).then(
            this.playerHandler.loaded.bind(this.playerHandler),
            function (errorCode) {
                this.playerState = PLAYER_STATE.ERROR;
                console.log('Remote media load error: ' +
                    CastPlayer.getErrorMessage(errorCode));
            }.bind(this));
    }.bind(this);

    //...
};

이제 로컬 재생과 원격 재생 간에 전환하는 메서드를 추가합니다.

/**
 * This is a method for switching between the local and remote
 * players. If the local player is selected, setupLocalPlayer()
 * is run. If there is a cast device connected we run
 * setupRemotePlayer().
 */
CastPlayer.prototype.switchPlayer = function() {
    this.stopProgressTimer();
    this.resetVolumeSlider();
    this.playerHandler.stop();
    this.playerState = PLAYER_STATE.IDLE;
    if (cast && cast.framework) {
        if (this.remotePlayer.isConnected) {
            this.setupRemotePlayer();
            return;
        }
    }
    this.setupLocalPlayer();
};

마지막으로 Cast 오류 메시지를 처리할 메서드를 추가합니다.

/**
 * Makes human-readable message from chrome.cast.Error
 * @param {chrome.cast.Error} error
 * @return {string} error message
 */
CastPlayer.getErrorMessage = function(error) {
  switch (error.code) {
    case chrome.cast.ErrorCode.API_NOT_INITIALIZED:
      return 'The API is not initialized.' +
        (error.description ? ' :' + error.description : '');
    case chrome.cast.ErrorCode.CANCEL:
      return 'The operation was canceled by the user' +
        (error.description ? ' :' + error.description : '');
    case chrome.cast.ErrorCode.CHANNEL_ERROR:
      return 'A channel to the receiver is not available.' +
        (error.description ? ' :' + error.description : '');
    case chrome.cast.ErrorCode.EXTENSION_MISSING:
      return 'The Cast extension is not available.' +
        (error.description ? ' :' + error.description : '');
    case chrome.cast.ErrorCode.INVALID_PARAMETER:
      return 'The parameters to the operation were not valid.' +
        (error.description ? ' :' + error.description : '');
    case chrome.cast.ErrorCode.RECEIVER_UNAVAILABLE:
      return 'No receiver was compatible with the session request.' +
        (error.description ? ' :' + error.description : '');
    case chrome.cast.ErrorCode.SESSION_ERROR:
      return 'A session could not be created, or a session was invalid.' +
        (error.description ? ' :' + error.description : '');
    case chrome.cast.ErrorCode.TIMEOUT:
      return 'The operation timed out.' +
        (error.description ? ' :' + error.description : '');
  }
};

이제 앱을 실행합니다. Cast 기기에 연결하고 동영상 재생을 시작합니다. 수신기에서 재생되는 동영상을 볼 수 있습니다.

7. Cast Connect 지원 추가

Cast Connect 라이브러리를 사용하면 기존 발신기 애플리케이션이 Cast 프로토콜을 통해 Android TV 애플리케이션과 통신할 수 있습니다. Cast Connect는 Cast 인프라를 기반으로 빌드되며, Android TV 앱은 수신기 역할을 합니다.

종속 항목

  • Chrome 브라우저 버전 M87 이상

Android 수신기 호환 설정

Android TV 애플리케이션(Android 수신기라고도 함)을 실행하려면 CastOptions 객체에서 androidReceiverCompatible 플래그를 true로 설정해야 합니다.

다음 코드를 initializeCastPlayer 함수의 CastVideos.js에 추가합니다.

var options = {};
...
options.androidReceiverCompatible = true;

cast.framework.CastContext.getInstance().setOptions(options);

실행 사용자 인증 정보 설정

발신자 측에서 CredentialsData를 지정하여 세션에 참여하는 사람을 나타낼 수 있습니다. credentials은 ATV 앱에서 이해할 수 있는 한 사용자가 정의할 수 있는 문자열입니다. CredentialsData는 실행 또는 참여 시간에만 Android TV 앱으로 전달됩니다. 연결된 상태에서 다시 설정하면 Android TV 앱으로 전송되지 않습니다.

실행 사용자 인증 정보를 설정하려면 실행 옵션이 설정된 후 언제든지 CredentialsData을 정의해야 합니다.

CastVideos.js 클래스의 initializeCastPlayer 함수에 다음 코드를 추가합니다.

cast.framework.CastContext.getInstance().setOptions(options);
...
let credentialsData = new chrome.cast.CredentialsData("{\"userId\": \"abc\"}");
cast.framework.CastContext.getInstance().setLaunchCredentialsData(credentialsData);
...

로드 요청에서 사용자 인증 정보 설정

웹 수신기 앱과 Android TV 앱이 credentials를 다르게 처리하는 경우 각각에 대해 별도의 사용자 인증 정보를 정의해야 할 수도 있습니다. 이 문제를 처리하려면 setupRemotePlayer 함수의 playerTarget.load 아래에 있는 CastVideos.js에 다음 코드를 추가합니다.

...
var request = new chrome.cast.media.LoadRequest(mediaInfo);
request.credentials = 'user-credentials';
request.atvCredentials = 'atv-user-credentials';
...

발신자가 전송하는 수신 앱에 따라 이제 SDK가 현재 세션에서 사용할 사용자 인증 정보를 자동으로 처리합니다.

Cast Connect 테스트

Chromecast with Google TV에 Android TV APK를 설치하는 단계는 다음과 같습니다.

  1. Android TV 기기의 IP 주소를 찾습니다. 일반적으로 설정 > 네트워크 및 인터넷 > (기기가 연결된 네트워크 이름)에서 사용할 수 있습니다. 오른쪽에는 세부정보와 네트워크에 연결된 기기의 IP가 표시됩니다.
  2. 기기의 IP 주소를 사용하여 터미널을 통해 ADB를 통해 기기에 연결합니다.
$ adb connect <device_ip_address>:5555
  1. 터미널 창에서 이 Codelab을 시작할 때 다운로드한 Codelab 샘플의 최상위 폴더로 이동합니다. 예를 들면 다음과 같습니다.
$ cd Desktop/chrome_codelab_src
  1. 다음을 실행하여 이 폴더의 .apk 파일을 Android TV에 설치합니다.
$ adb -s <device_ip_address>:5555 install android-tv-app.apk
  1. 이제 Android TV 기기의 내 앱 메뉴에 동영상 전송이라는 이름으로 앱이 표시됩니다.
  2. 전송 아이콘을 사용하거나 Chrome 브라우저의 드롭다운 메뉴에서 Cast..을 선택하여 업데이트된 웹 발신자 코드를 실행하고 Android TV 기기로 전송 세션을 설정합니다. 그러면 Android 수신기에서 Android TV 앱이 실행되고 Android TV 리모컨을 사용하여 재생을 제어할 수 있습니다.

8. 축하합니다

지금까지 Chrome 웹 앱에서 Cast SDK 위젯을 사용하여 동영상 앱을 Cast 지원으로 사용 설정하는 방법을 알아보았습니다.

자세한 내용은 Web Sender 개발자 가이드를 참고하세요.