# 유튜브 함께 보기

> 부관리자 이상이 고른 유튜브 영상을 채팅방의 모든 사람이 함께 보는 예제예요.

부관리자 이상이 유튜브 주소를 넣으면 채팅방의 모든 사람에게 같은 영상이 떠요. 채널로 영상 아이디를 보내고 저장해 두므로 나중에 들어온 사람도 지금 영상을 봐요.

## 코드 {#code}

아래 코드를 그대로 페이지에 넣으면 동작해요. 첫 줄은 `uchat.js`가 불러와지기 전에 이벤트를 걸어 두는 미리 등록 코드예요([비동기 로딩과 여러 채팅방](/doc/2.2)).

```html
<script>U=window.U=window.U||{},U.events=U.events||[],U.chat=function(n){return{on:function(e,t){U.events.push([n,e,t])},off:function(e){for(var t=U.events.length;t>0;)U.events[--t][0]==n&&U.events[t][1]==e&&U.events.splice(t,1)}}};</script>
<script async src="https://client.uchat.io/uchat.js"></script>
<u-chat room="my-room" style="display:inline-block;width:100%;max-width:400px;height:500px"></u-chat>

<script>
U.chat('my-room').on('after.create#youtube-demo', function (room) {
	var ui = room.skin;
	var channel = room.plugin.add('youtube-demo');
	var removeOverlay = null;

	function show(id) {
		if (removeOverlay) removeOverlay();
		removeOverlay = null;
		if (!id) return;
		removeOverlay = ui.mount.overlay({
			id: 'youtube-demo.player',
			layer: 'content-overlay',
			scope: 'message-list',
			mount: function (host) {
				host.style.pointerEvents = 'none'; // 빈 곳은 아래 대화를 누를 수 있게
				var frame = host.ownerDocument.createElement('iframe');
				frame.src = 'https://www.youtube-nocookie.com/embed/' + id + '?autoplay=1';
				frame.allow = 'autoplay; encrypted-media';
				frame.style.cssText =
					'position:absolute;top:8px;right:8px;width:320px;height:180px;border:0;pointer-events:auto';
				host.appendChild(frame);
				return function () {
					frame.remove();
				};
			},
		});
	}

	function videoId(text) {
		var m =
			/(?:youtu\.be\/|v=|embed\/|shorts\/)([A-Za-z0-9_-]{11})/.exec(text) ||
			/^([A-Za-z0-9_-]{11})$/.exec(text);
		return m ? m[1] : '';
	}

	// 받기: 부관리자 이상, 또는 서버(저장값·서버 API)가 보낸 것만
	channel.onReceived(function (room, data) {
		if (!data.user || data.user.auth < 2) return;
		var id = String(data.data[0] || '');
		if (id === '' || /^[A-Za-z0-9_-]{11}$/.test(id)) show(id);
	});

	ui.menu.add({
		id: 'youtube-demo.open',
		label: '유튜브',
		condition: function (ctx) {
			return ctx.room.my.auth >= 2;
		},
		onSelect: function () {
			ui.dialog.prompt('유튜브', '영상 주소를 넣으세요. 비우면 닫습니다.').then(function (text) {
				if (text === null) return; // 취소
				text = text.trim();
				var id = videoId(text);
				if (text && !id) return ui.dialog.alert('유튜브', '올바른 유튜브 주소가 아닙니다.');
				if (id) {
					channel.save([id]); // 나중에 들어올 사람을 위해 저장
					channel.send([id]); // 지금 있는 사람에게(나에게도 돌아온다)
				} else {
					channel.clear();
					channel.send(['']);
				}
			});
		},
	});
});

// 입장하거나 권한이 바뀌면 버튼 표시 조건을 다시 계산한다
function refreshMenu(room) {
	room.skin.menu.resolver.refresh();
}
U.chat('my-room').on('after.join#youtube-demo', refreshMenu);
U.chat('my-room').on('after.user.modi#youtube-demo', refreshMenu);
</script>
```

## 사용법 {#usage}

- 부관리자 이상에게 입력창 도구 모음에 **유튜브** 버튼이 보여요. 회원 연동을 쓰지 않는 채팅방이라면 `/login 관리자비밀번호`로 관리자가 되어 시험해 보세요.
- 주소를 넣으면 대화 영역 오른쪽 위에 플레이어가 뜨고, 빈 값을 넣으면 모두에게서 닫혀요.
- 새로 들어온 사람에게는 저장된 값이 입장할 때 전달되어 같은 영상이 떠요.

## 동작 원리 {#how-it-works}

| 단계 | 쓰는 API | 설명 |
| --- | --- | --- |
| 버튼 | `room.skin.menu.add` | `condition`으로 부관리자 이상에게만 보여요. 콜백은 동기 함수여야 해요 |
| 입력 | `room.skin.dialog.prompt` | 확인하면 넣은 글자, 취소하면 `null`을 돌려주는 Promise예요 |
| 전송 | `channel.send`, `channel.save` | `send`는 지금 접속한 모두(나 포함)에게, `save`는 나중에 들어올 사람을 위해 저장해요 |
| 표시 | `room.skin.mount.overlay` | 대화 목록 위에 겹치는 영역을 붙이고, 돌려받은 함수로 떼요 |

저장된 값은 입장할 때 서버가 권한 `4`로 보내 주므로 받는 쪽 권한 검사를 통과해요. 각 API의 세부는 [스킨 UI API](/doc/3.4)와 [채널 API와 한도](/doc/3.5)를 보세요.

## 서버에서 틀기 {#server-play}

[서버 REST API](/doc/3.6)로 같은 채널에 영상 아이디를 보내면 사이트 관리 화면 같은 곳에서도 틀 수 있어요. 서버가 보낸 데이터는 권한이 `4`라서 위 코드의 권한 검사를 통과해요.

```sh
curl -X POST https://api.uchat.io/v2/rooms/room_xxxxxxxxxxxxxxxx/events \
  -H "Authorization: Bearer uchat_..." \
  -H "Content-Type: application/json" \
  -d '{"plugin":"youtube-demo","save":true,"data":["dQw4w9WgXcQ"]}'
```

`"save": true`로 보내면 채팅방에 저장돼서 나중에 들어온 사람도 같은 영상을 봐요. 채팅방에 아무도 없을 때 보내도 저장돼요. 닫으려면 `"data":[""]`를 저장해요. API 키는 관리 페이지 **API** 메뉴에서 발급해요.

## 짚어 볼 점 {#notes}

- 누구나 같은 채널로 데이터를 보낼 수 있어요. 받은 데이터는 보낸 사람의 권한(`data.user.auth`)과 모양을 꼭 확인해요.
- 겹치는 영역이 대화를 가리지 않도록 host는 클릭이 통과하게 하고, 플레이어에만 `pointer-events:auto`를 줘요.
- `save`는 부관리자 이상만 쓸 수 있어요. 무료 채팅방은 채널 하나에만 저장할 수 있어 다른 채널에 저장하면 기존 저장값이 지워져요.
- 무료 채팅방은 채널 전송량에 한도가 있어요. 전체 전송은 접속자 수만큼 차감되며 넘으면 오류 `428`이 나요([채널 API와 한도](/doc/3.5)).
