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 브라우저
  • Firebase 호스팅 또는 ngrok와 같은 HTTPS 호스팅 서비스
  • 인터넷에 액세스할 수 있도록 설정된 Chromecast 또는 Android TV와 같은 Google Cast 기기
  • HDMI 입력 단자가 있는 TV 또는 모니터
  • Cast Connect 통합을 테스트하려면 Chromecast with Google TV가 필요하지만 나머지 Codelab에서는 선택사항입니다. Chromecast가 없는 경우 이 튜토리얼의 끝부분에 나오는 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에 정의된 기본 뷰 1개와 기본 컨트롤러 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는 모든 전송 기능을 설정하는 메서드입니다. 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']에 할당된 콜백에서 호출됩니다. 이 SDK는 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 프레임워크에서 자동으로 처리됩니다.

전송 세션은 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에 의해 자동으로 생성됩니다. Codelab 앞부분에 나온 것처럼 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 수신기라고도 하는 Android TV 애플리케이션을 실행하려면 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를 정의해야 합니다.

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

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 사용 설정하는 방법을 알아보았습니다.

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