WebRTC로 CameraStream 구현

1. 시작하기 전에

CameraStream 특성은 스마트 디스플레이, Chromecast 기기, 스마트폰으로 동영상 피드를 스트리밍하는 기능이 있는 기기에 속합니다. 이제 WebRTC 프로토콜이 CameraStream 트레잇 내에서 지원되므로 카메라 기기에서 Google Nest 디스플레이 기기로의 시작 및 스트리밍 지연 시간을 크게 줄일 수 있습니다.

Google Nest 디스플레이 기기로 스트리밍 중인 카메라 기기

기본 요건

학습할 내용

  • 스마트 홈 클라우드 서비스를 배포하는 방법
  • Google 어시스턴트에 서비스를 연결하는 방법
  • WebRTC 프로토콜을 사용하여 Google Nest 디스플레이 기기로 스트리밍하는 방법

필요한 항목

  • 웹브라우저(예: Chrome)
  • Google Home 앱이 설치된 iOS 또는 Android 기기
  • Node.js 버전 10.16 이상
  • Firebase Blaze (사용한 만큼만 지불) 요금제
  • 풀 HD 해상도를 지원하는 내장 또는 외장 웹캠 기기
  • Google Nest 디스플레이 기기입니다.

2. 시작하기

Firebase CLI 설치

Firebase CLI를 사용하면 웹 앱을 로컬에서 제공하고 Firebase 호스팅에 배포할 수 있습니다.

Firebase CLI를 설치하려면 다음 단계를 따르세요.

  1. 터미널에서 Firebase CLI를 다운로드하고 설치합니다.
$ npm install -g firebase-tools
  1. CLI가 올바르게 설치되었는지 확인합니다.
$ firebase --version
  1. Google 계정으로 Firebase CLI를 승인합니다.
$ firebase login

작업 프로젝트 만들기 및 구성

  1. Actions 콘솔로 이동하여 새 프로젝트를 클릭합니다.
  2. 프로젝트 이름 텍스트 상자에 프로젝트 이름을 입력한 후 프로젝트 만들기를 클릭합니다.

Actions 콘솔의 New 프로젝트 대화상자

  1. 어떤 종류의 작업을 빌드하고 싶으신가요? 페이지에서 스마트 홈 > 빌드 시작을 클릭합니다. 프로젝트가 Actions 콘솔에서 열립니다.

Actions 콘솔의 개요 탭

  1. 개발 > 호출을 클릭합니다.
  2. 표시 이름 텍스트 상자에 작업 이름을 입력한 다음 저장을 클릭합니다. 나중에 설정할 기기가 있게 되면 이 이름이 Google Home 앱에 표시됩니다. 이 Codelab에서는 표시 이름으로 WebRTC Codelab를 입력했지만 다른 이름을 사용할 수도 있습니다.

Actions 콘솔의 호출 패널

  1. 작업을 클릭합니다.
  2. fulfillment URL 텍스트 상자에 https://example.com과 같은 자리표시자 URL을 입력합니다.

CameraStream 클라이언트 앱 실행

이 Codelab의 소스 코드에는 웹캠과 Google 스마트 홈 디스플레이 기기 간의 WebRTC 세션을 설정, 협상 및 관리하는 WebRTC 클라이언트가 포함되어 있습니다.

CameraStream WebRTC 클라이언트 앱을 실행하려면 다음 중 하나를 실행하세요.

  • 다음 버튼을 클릭하여 소스 코드를 개발 머신에 다운로드합니다.

  • 이 GitHub 저장소를 클론합니다.
$ git clone https://github.com/google-home/smarthome-camerastream-webrtc.git

코드에는 다음 디렉터리가 포함됩니다.

  • 빌드의 기반이 되는 시작 코드가 포함된 camerastream-start 디렉터리
  • 완료된 Codelab의 솔루션 코드가 포함된 camerastream-done 디렉터리

camerastream-start 디렉터리에는 다음과 같은 하위 디렉터리가 포함되어 있습니다.

  • 카메라 기기의 상태를 쉽게 제어하고 모니터링할 수 있는 프런트엔드 UI가 포함된 public 하위 디렉터리
  • functions 하위 디렉터리: Firebase용 Cloud Functions와 실시간 데이터베이스로 카메라를 관리하는 완전하게 구현된 클라우드 서비스가 포함되어 있습니다.

시작 코드에는 다음 예와 같이 코드를 추가하거나 변경해야 하는 위치를 나타내는 TODO 주석이 포함되어 있습니다.

// TODO: Implement full SYNC response.

Firebase에 연결

  1. camerastream-start 디렉터리로 이동한 후 작업 프로젝트로 Firebase CLI를 설정합니다.
$ cd camerastream-start
$ firebase use PROJECT_ID
  1. camerastream-start 디렉터리에서 functions 폴더로 이동한 후 필요한 모든 종속 항목을 설치합니다.
$ cd functions
$ npm install
  1. 다음 메시지가 표시되면 무시합니다. 이 경고는 이전 종속 항목으로 인해 표시됩니다. 자세한 내용은 이 GitHub 문제를 참고하세요.
found 5 high severity vulnerabilities
  run `npm audit fix` to fix them, or `npm audit` for details
  1. Firebase 프로젝트를 초기화합니다.
$ firebase init
  1. 함수호스팅을 선택합니다. 이렇게 하면 프로젝트에 필요한 API와 기능이 초기화됩니다.
? Which Firebase CLI features do you want to set up for this folder? Press Space to select features, then Enter to confirm your choices. 
❯◯ Realtime Database: Configure a security rules file for Realtime Database and (optionally) provision default instance
 ◯ Firestore: Deploy rules and create indexes for Firestore
 ◉ Functions: Configure a Cloud Functions directory and its files
 ◉ Hosting: Configure files for Firebase Hosting and (optionally) set up GitHub Action deploys
 ◯ Hosting: Set up GitHub Action deploys
 ◯ Storage: Configure a security rules file for Cloud Storage
 ◯ Extensions: Set up an empty Extensions manifest
  1. 기본 파일로 Cloud Functions를 구성하고 프로젝트 샘플의 기존 index.jspackage.json 파일을 덮어쓰지 않도록 합니다.
? Would you like to initialize a new codebase, or overwrite an existing one?
Overwrite

? What language would you like to use to write Cloud Functions? 
JavaScript

? File functions/package.json already exists. Overwrite? 
No

? File functions/index.js already exists. Overwrite? 
No

? Do you want to install dependencies with npm now? 
Yes
  1. 프로젝트 코드의 public 디렉터리로 호스팅을 구성하고 기존 index.html 파일을 사용합니다.
? What do you want to use as your public directory? 
public

? Configure as a single-page app (rewrite all urls to /index.html)? 
Yes

? Set up automatic builds and deploys with GitHub?
No

? File public/index.html already exists. Overwrite?
 No

3. Exchange 세션 설명 프로토콜 (SDP) 메시지

SDP 메시지 교환은 WebRTC 스트림 설정에서 중요한 단계입니다. SDP는 멀티미디어 세션의 특성을 설명하는 텍스트 기반 프로토콜입니다. 이것은 WebRTC에서 사용된 코덱, 참가자의 IP 주소, 미디어 전송에 사용되는 포트와 같은 P2P 연결의 매개변수를 협상하는 데 사용됩니다.

실시간 데이터베이스를 호스트로 사용하여 웹캠과 스마트 홈 CameraStream 클라이언트 앱 간에 SDP 메시지를 교환하려면 다음 단계를 따르세요.

  1. Firebase Console에서 빌드 > 실시간 데이터베이스 > 데이터베이스 만들기를 클릭합니다.

Firebase Console의 실시간 데이터베이스 페이지

  1. 실시간 데이터베이스 위치 드롭다운 메뉴에서 데이터베이스를 호스팅할 적절한 위치를 선택합니다.

데이터베이스 설정 대화상자의 실시간 데이터베이스 위치 드롭다운 메뉴

  1. 테스트 모드에서 시작을 선택한 다음 사용 설정을 클릭합니다. 실시간 데이터베이스를 사용 설정한 경우 CameraStream 클라이언트 앱에서 데이터베이스를 참조할 수 있어야 합니다.
  1. Firebase Console에서 513f2be95dcd7896.png 프로젝트 설정 > 프로젝트 설정 > e584a9026e2b407f.png웹 앱에 Firebase 추가를 선택하여 설정 워크플로를 시작합니다.
  2. Firebase 프로젝트에 앱을 이미 추가한 경우 앱 추가를 클릭하여 플랫폼 옵션을 표시합니다.
  3. 앱의 닉네임(예: My web app)을 입력한 다음 앱 등록을 클릭합니다.
  4. Firebase SDK 추가 섹션에서 <script> 태그 사용을 선택합니다.
  5. firebasebaseConfig 객체의 값을 복사하여 camaerastream-start/public/webrtc_generator.js 파일에 붙여넣습니다.
const firebaseConfig = {
  apiKey: "XXXXX",
  authDomain: "XXXXX",
  projectId: "XXXXX",
  storageBucket: "XXXXX",
  messagingSenderId: "XXXXX",
  appId: "XXXXX",
  measurementId: "XXXXX"
};
  1. 콘솔로 이동을 클릭하여 절차를 완료합니다. 프로젝트 설정 페이지에 새로 생성된 웹 앱이 표시됩니다.

4. WebRTC 카메라 만들기

이제 작업을 구성했으므로 클라우드 서비스에서 다음 인텐트를 처리해야 합니다.

  • 어시스턴트가 사용자가 연결한 기기를 파악하려고 할 때 발생하는 SYNC 인텐트입니다. 이 인텐트는 사용자가 계정을 연결하면 서비스로 전송됩니다. 사용자 기기 및 관련 기능에 관한 JSON 페이로드로 응답해야 합니다.
  • 어시스턴트가 사용자를 대신하여 기기를 제어하려고 할 때 발생하는 EXECUTE/QUERY 인텐트입니다. 요청된 각 기기의 실행 상태가 포함된 JSON 페이로드로 응답해야 합니다.

이 섹션에서는 이러한 인텐트를 처리하기 위해 이전에 배포한 함수를 업데이트합니다.

SYNC 응답 업데이트

  1. functions/index.js 파일로 이동합니다. 어시스턴트의 요청에 응답하기 위한 코드가 포함되어 있습니다.
  2. 기기의 메타데이터 및 기능을 반환하도록 SYNC 인텐트를 수정합니다.

index.js

app.onSync((body) => {
  return {
    requestId: body.requestId,
    payload: {
      agentUserId: USER_ID,
      devices: [{
        id: 'camera',
        type: 'action.devices.types.CAMERA',
        traits: [
          'action.devices.traits.OnOff',
          'action.devices.traits.CameraStream',
        ],
        name: {
          defaultNames: ['My WebRTC Camera],
          name: 'Camera',
          nicknames: ['Camera'],
        },
        deviceInfo: {
          manufacturer: 'Acme Co',
          model: 'acme-camera',
          hwVersion: '1.0',
          swVersion: '1.0.1',
        },
        willReportState: false,
        attributes: {
          cameraStreamSupportedProtocols:['webrtc'],
          cameraStreamNeedAuthToken: true, 
          cameraStreamSupportsPreview: true
        },
      }],
    },
  };
});

EXECUTE 인텐트 처리

EXECUTE 인텐트는 기기 상태를 업데이트하는 명령어를 처리합니다. 응답은 각 명령어의 상태(예: SUCCESS, ERROR 또는 PENDING)와 새 기기 상태를 반환합니다.

  • EXECUTE 인텐트를 처리하려면 functions/index.js 파일에서 Firebase 프로젝트의 signaling 엔드포인트를 반환하도록 EXECUTE 인텐트를 수정합니다.

index.js

app.onExecute(async (body,headers) => {
  var array = headers.authorization.split(' ');
  var snapshot = await firebaseRef.ref('/userId/'+array[1]).once('value');
  var offerGenLocation = snapshot.val().type;
  const {requestId} = body;

  var result = {
    status: 'SUCCESS',
    states: {
      cameraStreamProtocol: 'webrtc',
      cameraStreamSignalingUrl:'https://us-central1-<project-id>.cloudfunctions.net/signaling?token='+array[1], // TODO: Add Firebase hosting URL
      cameraStreamIceServers: '',
      cameraStreamOffer:'',
      cameraStreamAuthToken:'',
    },
    ids: [ 
      'camera'
    ],
  };
  
  return {
    requestId: requestId,
    payload: {
      commands: [result],
    },
  };

교차 출처 리소스 공유 (CORS) 처리

  • POST 메서드를 사용하여 SDP를 전송하는 CORS를 처리하려면 Firebase 호스팅 URL을 functions/index.js 파일의 allowlist 배열에 추가합니다.

index.js

'use strict';

const functions = require('firebase-functions');
const {smarthome} = require('actions-on-google');
const {google} = require('googleapis');
const util = require('util');
const admin = require('firebase-admin');

var allowList = ['https:www.gstatic.com','https://<project-id>.web.app']; //TODO Add Firebase hosting URL.

CORS에 대한 자세한 내용은 교차 출처 리소스 공유 (CORS)를 참조하세요.

스트림 종료 처리

  • WebRTC 스트림 종료를 처리하려면 public/webrtc_generator.js 파일에 Firebase 'signaling' 함수 URL을 추가합니다.

webrtc_generator.js

terminateButton.onclick = function(){
  console.log('Terminating Stream!!')
  var signalingURL = 'https://us-central1-<project-id>.cloudfunctions.net/signaling'; //TODO Add Firebase hosting URL 
   var http = new XMLHttpRequest();

Firebase에 배포

  • Firebase에 배포하려면 Firebase CLI를 사용하여 업데이트된 클라우드 처리를 배포합니다.
$ firebase deploy

이 명령어는 웹 앱 하나와 여러 Firebase용 Cloud Functions를 배포합니다.

...

✔ Deploy complete!

Project Console: https://console.firebase.google.com/project/<project-id>/overview
Hosting URL: https://<project-id>.web.app

계정 연결 사용 설정

프로젝트가 배포된 후 계정 연결을 사용 설정하려면 다음 단계를 따르세요.

  1. 작업 콘솔에서 개발 > 계정 연결을 선택합니다.
  2. OAuth 클라이언트 정보 섹션에서 해당 텍스트 상자에 다음 정보를 입력합니다.

Client ID

ABC123

클라이언트 비밀번호

DEF456

승인 URL

https://us-central1-{project-id}.cloudfunctions.net/fakeauth

토큰 URL

https://us-central1-{project-id}.cloudfunctions.net/faketoken

Actions 콘솔의 계정 연결 페이지

  1. 저장 > 테스트를 클릭합니다.

5. 가상 WebRTC 카메라 테스트

  1. Firebase 프로젝트를 배포했을 때 표시된 호스팅 URL로 이동합니다. CameraStream 클라이언트 앱인 다음 인터페이스가 표시됩니다.

CameraStream 클라이언트 앱 인터페이스

  1. 로컬 동영상 해상도 패널에서 원하는 동영상을 선택합니다.
  2. CameraStream 클라이언트 앱에 웹캠 및 마이크에 액세스할 수 있는 권한을 부여합니다. 웹캠 동영상 피드가 클라이언트에 나타납니다.
  1. Google Home 앱에서 추가 > Works with Google을 탭합니다.

Google Home 앱의 기기 설정 페이지

  1. 방금 만든 작업을 검색한 다음 선택합니다.

Google Home 앱의 스마트 홈 작업

  1. 나중에 필요하므로 고유한 5자리 영숫자 코드를 기록해 둡니다.

고유한 5자리 영숫자 코드입니다.

  1. 돌아가기를 탭합니다. WebRTC 카메라가 Google Home 앱의 집에 추가됩니다.

WebRTC 스트림 시작

  1. CameraStream 클라이언트 앱 웹페이지에서 계정 연결 토큰 값 텍스트 상자에 마지막 섹션의 영숫자 코드를 입력한 다음 제출을 클릭합니다.

계정 연결 토큰 값 텍스트 상자

  1. Google 스마트 디스플레이 기기에서 WebRTC 세션을 시작하려면 다음 중 하나를 실행하세요.
  • "Hey Google, WebRTC 카메라 스트리밍해 줘"라고 말합니다.
  • Google 스마트 디스플레이 기기에서 홈 컨트롤 > 카메라 > WebRTC 카메라를 탭합니다.

Google 스마트 홈 CameraStream 클라이언트 앱에서 Offer SPD와 Answer SDP가 성공적으로 생성되고 교환된 것을 확인할 수 있습니다. WebRTC를 통해 웹캠의 이미지가 Google 스마트 디스플레이 기기로 스트리밍됩니다.

6. 축하합니다

수고하셨습니다 WebRTC 프로토콜을 사용하여 웹캠에서 Google Nest 디스플레이 기기로 스트리밍하는 방법을 알아봤습니다.

자세히 알아보기