ChatReader
in package
implements
EventEmitterInterface
uses
EventEmitterTrait
Reads one live chat as it happens, and keeps reading through whatever the network does.
- Streaming: by default it holds
liveChatMessages.streamopen, and YouTube pushes each message as it is posted. A stream the server closes is reopened where it left off, with the lastnextPageToken, and one that has been silent for five minutes is reopened too, because a connection the network dropped looks exactly like a quiet chat. - Polling:
liveChatMessages.listat the interval YouTube asks for. It is what the reader falls back to if the stream is refused as unsupported, and it can be chosen outright withmode. - History: joining a chat sends its recent history first. Messages posted before the reader joined are not emitted, so a restart does not replay the last few minutes into wherever they are relayed.
- Quota: before each request it checks the day's quota, reserve kept
back. When polling, it slows down when the pace YouTube asks for would run
the quota out within
budget_hours. When nothing is left but the reserve, it pauses until the reset at midnight Pacific. - Dropped connections: it retries with the same back-off as TwitchPHP's chat client, a second to a minute over about five minutes, then every five minutes. reconnect() tries again at once.
Events:
| Event | Arguments | When |
|---|---|---|
chat.item |
LiveChatMessage |
Anything new in the chat, whatever its type. |
chat.message |
LiveChatMessage |
A text message. |
chat.superchat |
LiveChatMessage |
A Super Chat or Super Sticker. |
chat.member |
LiveChatMessage |
A new member, a milestone, or gifted memberships. |
chat.gift |
LiveChatMessage |
A virtual gift. |
chat.poll |
LiveChatMessage |
A poll, or a change to one. |
chat.banned |
LiveChatMessage |
A moderator banned someone. |
chat.deleted |
string message id, LiveChatMessage |
A tombstone: a message that has been deleted. |
chat.offline |
CarbonImmutable |
The stream went offline. The chat may stay open a while. |
chat.ended |
string why |
The chat is over. The reader stops. |
chat.connected |
Reading, at first and after every recovery. | |
chat.disconnected |
int status, string why |
Reading stopped. |
chat.reconnecting |
int attempt, float delay |
Another attempt is coming. |
chat.reconnect_failed |
int attempts |
The quick retries are spent. Once an outage. |
chat.paused |
CarbonImmutable resumes |
Only the quota reserve is left. Once a pause. |
chat.resumed |
The quota reset. | |
chat.slowed |
float seconds |
Polling slowed to spread the quota over the day. |
chat.mode |
string mode |
Streaming was refused, so it polls instead. |
YouTube stopped reporting deleted messages as they happen: a deletion shows
up only as a tombstone in a later listing, and a moderator's ban arrives as
chat.banned.
Tags
Table of Contents
Interfaces
- EventEmitterInterface
Constants
- DEFAULTS : mixed = [ // `stream`, or `poll` to never try streaming...
- MODE_POLL : mixed = 'poll'
- MODE_STREAM : mixed = 'stream'
- RETRY_DELAYS : mixed = [1, 2, 5, 10, 20, 30, 60, 60, 60, 60]
- Seconds before each quick retry. After the last, every `keep_trying_every`.
- EVENTS : mixed = [\YouTube\Parts\LiveChatMessageSnippet::TYPE_TE...
- Message types, and the event each is emitted as besides `chat.item`.
Properties
- $connected : bool
- $ended : bool
- $failureAnnounced : bool
- $failures : int
- Failed attempts in a row.
- $generation : int
- Bumped for every attempt, so the callbacks of an abandoned one do nothing.
- $historyBefore : ?float
- Messages posted before this Unix time are history. Null once past it.
- $idleTimer : ?TimerInterface
- $liveChatId : string
- $mode : string
- $offline : bool
- $options : array<string, mixed>
- $pageToken : ?string
- Where to carry on from, once there is somewhere.
- $paused : bool
- $reconnecting : ?Deferred
- Callers of {@see reconnect()} waiting for it.
- $running : bool
- $seen : array<string, true>
- $slowed : bool
- $stream : ?ResponseStream
- $streamHeard : bool
- $streamOpenedAt : float
- When the open stream was opened, and whether it has sent anything.
- $timer : ?TimerInterface
- $youtube : YouTube
Methods
- __construct() : mixed
- getLiveChatId() : string
- getMode() : string
- `stream`, or `poll` when it was chosen or streaming was refused.
- getPageToken() : ?string
- Where the next request carries on from.
- hasEnded() : bool
- isConnected() : bool
- reconnect() : PromiseInterface<string|int, null>
- Tries again now, rather than when the next retry is due. Resolves when that attempt works, at once if reading never stopped, and rejects with the reason when it fails. The quota reserve may be spent on it: a person asked. Retrying carries on either way.
- start() : void
- Joins the chat and starts reading.
- stop() : void
- Stops reading. {@see start()} carries on from where it stopped.
- abandon() : void
- Drops whatever is in flight, so its callbacks do nothing.
- armIdle() : void
- cancelIdle() : void
- cancelTimer() : void
- end() : void
- The chat is over: nothing more will come.
- failed() : void
- fetch() : void
- isHistory() : bool
- Whether a message was posted before the reader joined.
- lost() : void
- message() : void
- next() : void
- Schedules the next request.
- now() : float
- openStream() : void
- page() : void
- pause() : void
- Only the reserve is left: wait for midnight Pacific.
- poll() : void
- pollInterval() : float
- How long to wait before the next poll: what YouTube asks for, or longer if that pace would spend the quota within `budget_hours`, or before it resets if that comes first.
- remember() : void
- scheduleRetry() : void
- settleReconnect() : void
- streamClosed() : void
- The open stream closed: by the server's choice, which is carried on from at once, or with an error, which {@see failed()} deals with.
- streamingRefused() : bool
- Whether a failure says the stream itself is unavailable, not that the chat is.
- succeeded() : void
Constants
DEFAULTS
public
mixed
DEFAULTS
= [
// `stream`, or `poll` to never try streaming.
'mode' => self::MODE_STREAM,
// The parts of each message to ask for.
'part' => ['id', 'snippet', 'authorDetails'],
// The language of system messages and currency strings, e.g. `en`.
'hl' => null,
// The size, in pixels, of the avatars in `authorDetails`.
'profile_image_size' => null,
// Leave out what was posted before the reader joined.
'skip_history' => true,
// Seconds before joining that still count as new: clocks disagree a little.
'history_grace' => 10.0,
// Seconds between attempts once the quick retries are spent.
'keep_trying_every' => 300.0,
// Up to this fraction added to each retry delay, so bots do not retry in step.
'jitter' => 0.2,
// Seconds of silence after which a stream is reopened.
'stream_idle' => 300.0,
// The slowest the quota may make polling.
'max_poll_interval' => 60.0,
// Hours of polling the quota left should last: at the pace YouTube asks
// for until the rest would not, then slower. A stream rarely runs until
// midnight Pacific, so spreading the quota that far would slow every
// stream from its first minute.
'budget_hours' => 6.0,
// Message ids remembered, so a reconnect does not repeat one.
'remember' => 2000,
// The time, as a Unix timestamp, for tests.
'clock' => null,
]
MODE_POLL
public
mixed
MODE_POLL
= 'poll'
MODE_STREAM
public
mixed
MODE_STREAM
= 'stream'
RETRY_DELAYS
Seconds before each quick retry. After the last, every `keep_trying_every`.
public
mixed
RETRY_DELAYS
= [1, 2, 5, 10, 20, 30, 60, 60, 60, 60]
EVENTS
Message types, and the event each is emitted as besides `chat.item`.
private
mixed
EVENTS
= [\YouTube\Parts\LiveChatMessageSnippet::TYPE_TEXT_MESSAGE_EVENT => 'chat.message', \YouTube\Parts\LiveChatMessageSnippet::TYPE_SUPER_CHAT_EVENT => 'chat.superchat', \YouTube\Parts\LiveChatMessageSnippet::TYPE_SUPER_STICKER_EVENT => 'chat.superchat', \YouTube\Parts\LiveChatMessageSnippet::TYPE_NEW_SPONSOR_EVENT => 'chat.member', \YouTube\Parts\LiveChatMessageSnippet::TYPE_MEMBER_MILESTONE_CHAT_EVENT => 'chat.member', \YouTube\Parts\LiveChatMessageSnippet::TYPE_MEMBERSHIP_GIFTING_EVENT => 'chat.member', \YouTube\Parts\LiveChatMessageSnippet::TYPE_GIFT_MEMBERSHIP_RECEIVED_EVENT => 'chat.member', \YouTube\Parts\LiveChatMessageSnippet::TYPE_GIFT_EVENT => 'chat.gift', \YouTube\Parts\LiveChatMessageSnippet::TYPE_POLL_EVENT => 'chat.poll', \YouTube\Parts\LiveChatMessageSnippet::TYPE_USER_BANNED_EVENT => 'chat.banned']
Properties
$connected
private
bool
$connected
= false
$ended
private
bool
$ended
= false
$failureAnnounced
private
bool
$failureAnnounced
= false
$failures
Failed attempts in a row.
private
int
$failures
= 0
$generation
Bumped for every attempt, so the callbacks of an abandoned one do nothing.
private
int
$generation
= 0
$historyBefore
Messages posted before this Unix time are history. Null once past it.
private
?float
$historyBefore
= null
$idleTimer
private
?TimerInterface
$idleTimer
= null
$liveChatId read-only
private
string
$liveChatId
$mode
private
string
$mode
$offline
private
bool
$offline
= false
$options
private
array<string, mixed>
$options
$pageToken
Where to carry on from, once there is somewhere.
private
?string
$pageToken
= null
$paused
private
bool
$paused
= false
$reconnecting
Callers of {@see reconnect()} waiting for it.
private
?Deferred
$reconnecting
= null
$running
private
bool
$running
= false
$seen
private
array<string, true>
$seen
= []
Recent message ids, oldest first.
$slowed
private
bool
$slowed
= false
$stream
private
?ResponseStream
$stream
= null
$streamHeard
private
bool
$streamHeard
= false
$streamOpenedAt
When the open stream was opened, and whether it has sent anything.
private
float
$streamOpenedAt
= 0.0
$timer
private
?TimerInterface
$timer
= null
$youtube read-only
private
YouTube
$youtube
Methods
__construct()
public
__construct(YouTube $youtube, string $liveChatId[, array<string, mixed> $options = [] ]) : mixed
Parameters
- $youtube : YouTube
- $liveChatId : string
- $options : array<string, mixed> = []
-
See DEFAULTS.
getLiveChatId()
public
getLiveChatId() : string
Return values
stringgetMode()
`stream`, or `poll` when it was chosen or streaming was refused.
public
getMode() : string
Return values
stringgetPageToken()
Where the next request carries on from.
public
getPageToken() : ?string
Return values
?stringhasEnded()
public
hasEnded() : bool
Return values
boolisConnected()
public
isConnected() : bool
Return values
boolreconnect()
Tries again now, rather than when the next retry is due. Resolves when that attempt works, at once if reading never stopped, and rejects with the reason when it fails. The quota reserve may be spent on it: a person asked. Retrying carries on either way.
public
reconnect() : PromiseInterface<string|int, null>
Return values
PromiseInterface<string|int, null>start()
Joins the chat and starts reading.
public
start() : void
stop()
Stops reading. {@see start()} carries on from where it stopped.
public
stop() : void
abandon()
Drops whatever is in flight, so its callbacks do nothing.
private
abandon() : void
armIdle()
private
armIdle(int $generation) : void
Parameters
- $generation : int
cancelIdle()
private
cancelIdle() : void
cancelTimer()
private
cancelTimer() : void
end()
The chat is over: nothing more will come.
private
end(string $why) : void
Parameters
- $why : string
failed()
private
failed(Throwable $e) : void
Parameters
- $e : Throwable
fetch()
private
fetch([bool $force = false ]) : void
Parameters
- $force : bool = false
-
Spend the reserve too: a person asked for this.
isHistory()
Whether a message was posted before the reader joined.
private
isHistory(LiveChatMessage $message) : bool
Parameters
- $message : LiveChatMessage
Return values
boollost()
private
lost(Throwable $e) : void
Parameters
- $e : Throwable
message()
private
message(LiveChatMessage $message) : void
Parameters
- $message : LiveChatMessage
next()
Schedules the next request.
private
next(float $delay[, bool $force = false ]) : void
Parameters
- $delay : float
- $force : bool = false
now()
private
now() : float
Return values
floatopenStream()
private
openStream() : void
page()
private
page(LiveChatMessageListResponse $page) : void
Parameters
- $page : LiveChatMessageListResponse
pause()
Only the reserve is left: wait for midnight Pacific.
private
pause() : void
poll()
private
poll() : void
pollInterval()
How long to wait before the next poll: what YouTube asks for, or longer if that pace would spend the quota within `budget_hours`, or before it resets if that comes first.
private
pollInterval(LiveChatMessageListResponse $page) : float
Parameters
- $page : LiveChatMessageListResponse
Return values
floatremember()
private
remember(string $id) : void
Parameters
- $id : string
scheduleRetry()
private
scheduleRetry() : void
settleReconnect()
private
settleReconnect([?Throwable $failure = null ]) : void
Parameters
- $failure : ?Throwable = null
streamClosed()
The open stream closed: by the server's choice, which is carried on from at once, or with an error, which {@see failed()} deals with.
private
streamClosed(bool $withError) : void
Parameters
- $withError : bool
streamingRefused()
Whether a failure says the stream itself is unavailable, not that the chat is.
private
streamingRefused(Throwable $e) : bool
Parameters
- $e : Throwable
Return values
boolsucceeded()
private
succeeded() : void