# YouTubeを一緒に見る

> モデレーター以上が選んだYouTube動画を、チャットルームの全員で一緒に見るサンプルです。

モデレーター以上がYouTubeのURLを入力すると、チャットルームの全員に同じ動画が表示されます。動画IDをチャンネルで送って保存しておくので、あとから入室した人にもいまの動画が表示されます。

## コード {#code}

次のコードをそのままページに入れれば動作します。最初の行は、`uchat.js` が読み込まれる前にイベントを登録しておくための事前登録コードです([非同期読み込みと複数のチャットルーム](/ja/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: 'YouTube',
		condition: function (ctx) {
			return ctx.room.my.auth >= 2;
		},
		onSelect: function () {
			ui.dialog.prompt('YouTube', '動画の URL を入力してください。空にすると閉じます。').then(function (text) {
				if (text === null) return; // キャンセル
				text = text.trim();
				var id = videoId(text);
				if (text && !id) return ui.dialog.alert('YouTube', '正しい YouTube の URL ではありません。');
				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}

- モデレーター以上には、入力欄のツールバーに **YouTube** ボタンが表示されます。会員連携を使っていないチャットルームなら、`/login 管理者パスワード` で管理者になって試してください。
- URLを入力すると会話領域の右上にプレーヤーが表示され、空の値を入力すると全員の画面から閉じます。
- 新しく入室した人には保存された値が入室時に届き、同じ動画が表示されます。

## 仕組み {#how-it-works}

| 段階 | 使うAPI | 説明 |
| --- | --- | --- |
| ボタン | `room.skin.menu.add` | `condition` でモデレーター以上にだけ表示します。コールバックは同期関数である必要があります |
| 入力 | `room.skin.dialog.prompt` | OKなら入力した文字列、キャンセルなら `null` を返すPromiseです |
| 送信 | `channel.send`, `channel.save` | `send` はいま接続している全員(自分を含む)に送り、`save` はあとから入室する人のために保存します |
| 表示 | `room.skin.mount.overlay` | 会話一覧の上に重なる領域を付け、返された関数で外します |

保存された値は入室時にサーバーが権限 `4` で送るため、受信側の権限チェックを通過します。各APIの詳細は[スキンUI API](/ja/doc/3.4)と[チャンネルAPIと上限](/ja/doc/3.5)をご覧ください。

## サーバーから再生する {#server-play}

[サーバーREST API](/ja/doc/3.6)で同じチャンネルに動画IDを送れば、サイトの管理画面などからも再生できます。サーバーが送ったデータは権限が `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` はモデレーター以上だけが使えます。無料チャットルームは1つのチャンネルにしか保存できないため、別のチャンネルに保存すると既存の保存値は削除されます。
- 無料チャットルームはチャンネルの送信量に上限があります。全体送信は接続者数の分だけ差し引かれ、上限を超えるとエラー `428` になります([チャンネルAPIと上限](/ja/doc/3.5))。
