查詢在線人數與觀看人數
顯示在線或正在觀看的人數,用 presence 計算不重複的使用者,並透過 HTTP API 和 webhook 從伺服器讀取每個房間的人數。
「1,284 人在線」、「38 人正在觀看」、「房間已滿」:一個即時的人數讓產品顯得熱鬧,同一個數字也告訴你的團隊哪些房間最熱門、什麼時候該加派管理員、晚上的高峰有多高。你不必自己數連線。Hibira 本來就知道每個頻道有誰訂閱,而 Pusher 協定提供了四種查詢方式。
使用者得到什麼
- 隨著有人進出而跳動的「在線人數」或「正在觀看」。
- 一排頭像加上「還有 120 人」,同一個人開兩個分頁只算一次。
- 房間在有人嘗試加入之前就顯示「已滿」。
- 給你的團隊:一個列出最熱門房間的管理頁,以及高峰的歷史紀錄。

用到的功能
- 訂閱人數(
pusher:subscription_count),在瀏覽器裡取得公開與私人頻道的人數。 - Presence 頻道(
presence-)計算不重複的使用者,大型 presence 頻道(presence-large-)最多可容納 100,000 名成員。 - HTTP API(
GET /channels),讓你的伺服器隨時讀取人數。 - Webhook(
member_added、member_removed、subscription_count),人數變化時通知你,方便保留歷史紀錄。
在瀏覽器裡
pusher:subscription_count你的伺服器查詢
GET /channels?info=user_count你的伺服器收到通知
member_added算連線還是算人?
這是兩個不同的數字,值得刻意選擇:
subscription_count算的是連線。一個人開三個分頁就算三次。它在公開與私人頻道上都能用,不需要登入,適合公開頁面上的「正在觀看」計數。user_count算的是 presence 頻道上不重複的使用者,依據你的伺服器簽署的user_id。兩個分頁,或一支手機加一台筆電,都只算一次。只要「人」才是重點,例如座位上限或在線名單,就用它。
步驟 1:在瀏覽器裡顯示人數
在應用程式設定裡開啟 傳送訂閱人數。之後公開或私人頻道的每位訂閱者都會收到目前的人數,人數變動時最多每秒一次:
const stream = pusher.subscribe('stream-42');
stream.bind('pusher:subscription_count', ({ subscription_count }) => {
watching.textContent = subscription_count.toLocaleString();
});不寫程式也行,一行嵌入腳本可以做到同樣的事:<span data-hibira-online="stream-42">0</span> 人正在觀看。
步驟 2:用 presence 計算人數
在 presence 頻道上,每個人都會收到成員名單,所以人數已經在瀏覽器裡了:
const room = pusher.subscribe('presence-room-' + roomId);
const show = () => online.render(room.members.count, room.members);
room.bind('pusher:subscription_succeeded', show);
room.bind('pusher:member_added', show);
room.bind('pusher:member_removed', show);Presence 頻道最多 100 名成員,因為每位成員都會收到每一次加入。如果是整個網站或大型活動,就讓每個頁面加入同一個大型 presence 頻道,搭配 @hibira-io/large-presence。它最多容納 100,000 名成員,每秒送一次精確人數和在場者的樣本:
import { LargePresence } from '@hibira-io/large-presence';
const site = LargePresence.subscribe(pusher, 'presence-large-site', { maxMembers: 12 });
site.on('ready', ({ count }) => badge.render(count, site.members));
site.on('change', ({ count }) => badge.render(count, site.members));步驟 3:從你的伺服器查詢
你的伺服器可以透過 HTTP API 讀取同樣的數字,任何相容 Pusher 的伺服器函式庫都可以。查詢單一頻道:
const res = await pusher.get({
path: '/channels/presence-room-42',
params: { info: 'user_count,subscription_count' },
});
const { occupied, user_count, subscription_count } = await res.json();一次查詢所有房間,最熱門的排在前面:
const res = await pusher.get({
path: '/channels',
params: { filter_by_prefix: 'presence-room-', info: 'user_count' },
});
const { channels } = await res.json();
// { "presence-room-7": { "user_count": 42 }, "presence-room-9": { "user_count": 17 } }
const busiest = Object.entries(channels)
.map(([name, { user_count }]) => ({ name, users: user_count }))
.sort((a, b) => b.users - a.users);要在簽署訂閱之前檢查座位上限,就在頻道授權路由裡讀取房間人數,滿了就拒絕:
app.post('/pusher/auth', requireLogin, async (req, res) => {
const { socket_id, channel_name } = req.body;
const room = await pusher.get({ path: `/channels/${channel_name}`, params: { info: 'user_count' } });
const { user_count } = await room.json();
if (user_count >= 8) return res.status(403).json({ error: 'room_full' });
res.json(pusher.authorizeChannel(socket_id, channel_name, { user_id: req.user.id, user_info: { name: req.user.name } }));
});發布事件時,也可以詢問當下有多少連線訂閱,知道這個事件送到了多少人:
const sent = await pusher.trigger('stream-42', 'goal', { minute: 81 }, { info: 'subscription_count' });
const { channels } = await sent.json();
console.log(`reached ${channels['stream-42'].subscription_count} viewers`);user_count 只能用在 presence 頻道;在頻道列表上查詢時需要加上 filter_by_prefix=presence-。把多個房間的人數相加,同時在兩個房間的人會被算兩次;要取得全站不重複的人數,請改讀 GET /channels/presence-large-site?info=user_count。
步驟 4:用 webhook 保留歷史紀錄
要畫出晚上高峰的圖表,或在房間清空時發出警示,就讓 Hibira 通知你的伺服器。在入口網站中為 member_added 和 member_removed(presence 頻道)、subscription_count(公開與私人頻道,開啟計數時),或 channel_occupied 和 channel_vacated 新增 webhook:
app.post('/hibira/webhooks', express.text({ type: '*/*' }), async (req, res) => {
const webhook = pusher.webhook({ headers: req.headers, rawBody: req.body });
if (!webhook.isValid()) return res.sendStatus(401);
for (const e of webhook.getEvents()) {
if (e.name === 'member_added') await db.online.add(e.channel, e.user_id);
if (e.name === 'member_removed') await db.online.remove(e.channel, e.user_id);
if (e.name === 'subscription_count') await db.viewers.record(e.channel, e.subscription_count);
}
res.sendStatus(200);
});小技巧
- 讀取人數不會用到你的訊息額度,只有發布的事件才算訊息。不過仍建議在伺服器上把 HTTP API 的結果快取幾秒,而不是每次頁面瀏覽都查一次。
- Webhook 可能延遲或重複送達。拿它來做歷史紀錄與警示;需要當下精確的數字時,就讀 HTTP API。
- 在熱門的公開頁面上顯示四捨五入的數字(「1.2k 在線」):跳動較少,依然有熱鬧的感覺。
- 人數以應用程式為單位。如果你有測試和正式兩個應用程式,各自只看得到自己的連線。
- 方案限制的是同時連線數,不是人數:每個方案允許的高峰請見價格。