11import type { Announced } from "../announced.ts" ;
2+ import { type Bandwidth , createBandwidth } from "../bandwidth.ts" ;
23import type { Broadcast } from "../broadcast.ts" ;
34import type { Established } from "../connection/established.ts" ;
45import * as Path from "../path.ts" ;
@@ -10,7 +11,9 @@ import { SessionInfo } from "./session.ts";
1011import { StreamId } from "./stream.ts" ;
1112import { Subscribe } from "./subscribe.ts" ;
1213import { Subscriber } from "./subscriber.ts" ;
13- import { type Version , versionName } from "./version.ts" ;
14+ import { Version , versionName } from "./version.ts" ;
15+
16+ const SEND_BW_POLL_INTERVAL = 100 ; // ms
1417
1518/**
1619 * Represents a connection to a MoQ server.
@@ -39,8 +42,11 @@ export class Connection implements Established {
3942 // Module for distributing tracks.
4043 #subscriber: Subscriber ;
4144
42- // Just to avoid logging when `close()` is called.
43- #closed = false ;
45+ /** Estimated send bitrate from the congestion controller. */
46+ readonly sendBandwidth ?: Bandwidth ;
47+
48+ /** Estimated receive bitrate from PROBE (moq-lite-03+ only). */
49+ readonly recvBandwidth ?: Bandwidth ;
4450
4551 /**
4652 * Creates a new Connection instance.
@@ -57,8 +63,19 @@ export class Connection implements Established {
5763 this . version = versionName ( version ) ;
5864 this . #version = version ;
5965
66+ // Send bandwidth is version-agnostic: depends on browser/QUIC support.
67+ const hasGetStats = typeof ( quic as unknown as { getStats ?: unknown } ) . getStats === "function" ;
68+ if ( hasGetStats ) {
69+ this . sendBandwidth = createBandwidth ( ) ;
70+ }
71+
72+ // Recv bandwidth requires PROBE support (not available in older drafts).
73+ if ( version !== Version . DRAFT_01 && version !== Version . DRAFT_02 ) {
74+ this . recvBandwidth = createBandwidth ( ) ;
75+ }
76+
6077 this . #publisher = new Publisher ( this . #quic, this . #version) ;
61- this . #subscriber = new Subscriber ( this . #quic, this . #version) ;
78+ this . #subscriber = new Subscriber ( this . #quic, this . #version, this . recvBandwidth ) ;
6279
6380 this . #run( ) ;
6481 }
@@ -67,9 +84,6 @@ export class Connection implements Established {
6784 * Closes the connection.
6885 */
6986 close ( ) {
70- if ( this . #closed) return ;
71-
72- this . #closed = true ;
7387 this . #publisher. close ( ) ;
7488 this . #subscriber. close ( ) ;
7589
@@ -82,62 +96,46 @@ export class Connection implements Established {
8296 }
8397
8498 async #run( ) : Promise < void > {
85- const session = this . #runSession( ) ;
86- const bidis = this . #runBidis( ) ;
87- const unis = this . #runUnis( ) ;
99+ const tasks : Promise < void > [ ] = [ this . #runSession( ) , this . #runBidis( ) , this . #runUnis( ) ] ;
100+
101+ if ( this . sendBandwidth ) {
102+ tasks . push ( this . #runSendBandwidth( this . sendBandwidth ) ) ;
103+ }
104+
105+ if ( this . recvBandwidth ) {
106+ tasks . push ( this . #subscriber. runProbe ( ) ) ;
107+ }
88108
89109 try {
90- await Promise . all ( [ session , bidis , unis ] ) ;
110+ await Promise . all ( tasks ) ;
91111 } catch ( err ) {
92- if ( ! this . #closed) {
93- console . error ( "fatal error running connection" , err ) ;
94- }
112+ console . error ( "fatal error running connection" , err ) ;
95113 } finally {
96114 this . close ( ) ;
97115 }
98116 }
99117
100- /**
101- * Publishes a broadcast to the connection.
102- * @param name - The broadcast path to publish
103- * @param broadcast - The broadcast to publish
104- */
105118 publish ( path : Path . Valid , broadcast : Broadcast ) {
106119 this . #publisher. publish ( path , broadcast ) ;
107120 }
108121
109- /**
110- * Gets the next announced broadcast.
111- */
112122 announced ( prefix = Path . empty ( ) ) : Announced {
113123 return this . #subscriber. announced ( prefix ) ;
114124 }
115125
116- /**
117- * Consumes a broadcast from the connection.
118- *
119- * @remarks
120- * If the broadcast is not found, a "not found" error will be thrown when requesting any tracks.
121- *
122- * @param broadcast - The path of the broadcast to consume
123- * @returns A Broadcast instance
124- */
125126 consume ( broadcast : Path . Valid ) : Broadcast {
126127 return this . #subscriber. consume ( broadcast ) ;
127128 }
128129
129130 async #runSession( ) {
130131 if ( ! this . #session) {
131- // moq-lite draft-03 doesn't use a session stream.
132132 return ;
133133 }
134134
135135 try {
136- // Receive messages until the connection is closed.
137136 for ( ; ; ) {
138137 const msg = await SessionInfo . decodeMaybe ( this . #session. reader , this . #version) ;
139138 if ( ! msg ) break ;
140- // TODO use the session info
141139 }
142140 } finally {
143141 console . debug ( "session stream closed" ) ;
@@ -147,9 +145,7 @@ export class Connection implements Established {
147145 async #runBidis( ) {
148146 for ( ; ; ) {
149147 const stream = await Stream . accept ( this . #quic) ;
150- if ( ! stream ) {
151- break ;
152- }
148+ if ( ! stream ) break ;
153149
154150 this . #runBidi( stream )
155151 . catch ( ( err : unknown ) => {
@@ -169,14 +165,11 @@ export class Connection implements Established {
169165 } else if ( typ === StreamId . Announce ) {
170166 const msg = await AnnounceInterest . decode ( stream . reader ) ;
171167 await this . #publisher. runAnnounce ( msg , stream ) ;
172- return ;
173168 } else if ( typ === StreamId . Subscribe ) {
174169 const msg = await Subscribe . decode ( stream . reader , this . #version) ;
175170 await this . #publisher. runSubscribe ( msg , stream ) ;
176- return ;
177171 } else if ( typ === StreamId . Probe ) {
178172 await this . #publisher. runProbe ( stream ) ;
179- return ;
180173 } else {
181174 throw new Error ( `unknown stream type: ${ typ . toString ( ) } ` ) ;
182175 }
@@ -187,9 +180,7 @@ export class Connection implements Established {
187180
188181 for ( ; ; ) {
189182 const stream = await readers . next ( ) ;
190- if ( ! stream ) {
191- break ;
192- }
183+ if ( ! stream ) break ;
193184
194185 this . #runUni( stream )
195186 . then ( ( ) => {
@@ -211,10 +202,29 @@ export class Connection implements Established {
211202 }
212203 }
213204
214- /**
215- * Returns a promise that resolves when the connection is closed.
216- * @returns A promise that resolves when closed
217- */
205+ async #runSendBandwidth( bandwidth : Bandwidth ) : Promise < void > {
206+ const quic = this . #quic as unknown as {
207+ getStats : ( ) => Promise < { estimatedSendRate : number | null } > ;
208+ } ;
209+
210+ return new Promise < void > ( ( resolve ) => {
211+ const id = setInterval ( async ( ) => {
212+ try {
213+ const stats = await quic . getStats ( ) ;
214+ bandwidth . set ( stats . estimatedSendRate ?? undefined ) ;
215+ } catch {
216+ clearInterval ( id ) ;
217+ resolve ( ) ;
218+ }
219+ } , SEND_BW_POLL_INTERVAL ) ;
220+
221+ void this . closed . then ( ( ) => {
222+ clearInterval ( id ) ;
223+ resolve ( ) ;
224+ } ) ;
225+ } ) ;
226+ }
227+
218228 get closed ( ) : Promise < void > {
219229 return this . #quic. closed . then ( ( ) => undefined ) ;
220230 }
0 commit comments