# Watch YouTube together

> An example where everyone in the chat room watches a YouTube video picked by a moderator or higher.

When a moderator or higher enters a YouTube URL, the same video appears for everyone in the chat room. The video ID is sent over a channel and saved, so people who join later also see the current video.

## Code {#code}

Put the code below on your page as is and it works. The first line is a pre-registration snippet that attaches events before `uchat.js` loads ([Async loading and multiple chat rooms](/en/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'; // Let clicks on empty space reach the chat below
				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] : '';
	}

	// Receive: only from moderators or higher, or from the server (saved value, server 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', 'Enter a video URL. Leave it empty to close.').then(function (text) {
				if (text === null) return; // Canceled
				text = text.trim();
				var id = videoId(text);
				if (text && !id) return ui.dialog.alert('YouTube', 'Not a valid YouTube URL.');
				if (id) {
					channel.save([id]); // Save for people who join later
					channel.send([id]); // To everyone here now (it comes back to me too)
				} else {
					channel.clear();
					channel.send(['']);
				}
			});
		},
	});
});

// Recalculate the button's display condition on join or when permissions change
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 {#usage}

- Moderators and higher see a **YouTube** button in the input toolbar. If your chat room doesn't use member integration, become an admin with `/login adminpassword` to try it.
- Enter a URL and a player appears at the top right of the chat area. Enter an empty value and it closes for everyone.
- People who join later receive the saved value on join and see the same video.

## How it works {#how-it-works}

| Step | API used | Description |
| --- | --- | --- |
| Button | `room.skin.menu.add` | `condition` shows it only to moderators and higher. Callbacks must be synchronous |
| Input | `room.skin.dialog.prompt` | A Promise that returns the entered text on OK, or `null` on cancel |
| Send | `channel.send`, `channel.save` | `send` goes to everyone connected now (including you); `save` stores it for people who join later |
| Display | `room.skin.mount.overlay` | Attaches an area overlaid on the message list, and detaches it with the returned function |

The server sends the saved value with permission `4` on join, so it passes the receiver's permission check. For details on each API, see [Skin UI API](/en/doc/3.4) and [Channel API and limits](/en/doc/3.5).

## Play from the server {#server-play}

Send a video ID to the same channel with the [Server REST API](/en/doc/3.6) and you can play it from places like your site's admin screen. Data sent by the server has permission `4`, so it passes the permission check in the code above.

```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"]}'
```

If you send with `"save": true`, the value is saved in the chat room, so people who join later see the same video. It is saved even if you send it when no one is in the chat room. To close the video, save `"data":[""]`. Issue the API key in the **API** menu of the admin page.

## Things to note {#notes}

- Anyone can send data to the same channel. Always check the sender's permission (`data.user.auth`) and the shape of the data you receive.
- So the overlay doesn't block the chat, let clicks pass through the host and give `pointer-events:auto` only to the player.
- Only moderators and higher can use `save`. A free chat room can save to only one channel, so saving to another channel deletes the existing saved value.
- Free chat rooms have a limit on channel traffic. A send to everyone counts once per connected user, and going over gives error `428` ([Channel API and limits](/en/doc/3.5)).
