@@ -22,11 +22,13 @@ type RecoveryHarness = {
2222 operation : PlayerOperation ;
2323 playbackRecovery : PlaybackRecovery ;
2424 playbackIntent : PlaybackIntent ;
25+ recoveryPositionMs : number ;
26+ loadTask : Promise < void > | null ;
2527 emitter : { emit : ( event : { type : string ; videoItag ?: number } ) => void } ;
2628 playerState : {
2729 value : TypeTypeMseState ;
2830 set : ( state : TypeTypeMseState ) => void ;
29- fail : ( error : Error ) => void ;
31+ fail : ( error : Error , recoveryPositionMs : number ) => void ;
3032 } ;
3133 deps : {
3234 loop : { stop : ( ) => void } ;
@@ -39,6 +41,8 @@ type RecoveryHarness = {
3941 switchSession : ( ...args : unknown [ ] ) => Promise < LoadedSession > ;
4042 handlePlaybackLoopError : ( error : Error , context : PlaybackLoopFailureContext ) => void ;
4143 reportPlaybackFailure : ( error : Error ) => void ;
44+ loadInitialSession : ( ) => Promise < void > ;
45+ load : ( ) => Promise < void > ;
4246 enqueueSessionTransition : < T > ( work : ( ) => Promise < T > ) => Promise < T > ;
4347 sessionTransition : Promise < void > ;
4448} ;
@@ -74,13 +78,16 @@ function harness(
7478 player : RecoveryHarness ;
7579 requests : CreatePlaybackRequest [ ] ;
7680 failures : Error [ ] ;
81+ failurePositions : number [ ] ;
7782 qualities : number [ ] ;
7883} {
7984 const player = Object . create ( TypeTypeMsePlayer . prototype ) as RecoveryHarness ;
8085 const requests : CreatePlaybackRequest [ ] = [ ] ;
8186 const failures : Error [ ] = [ ] ;
87+ const failurePositions : number [ ] = [ ] ;
8288 const qualities : number [ ] = [ ] ;
8389 player . destroyed = false ;
90+ player . loadTask = null ;
8491 player . sessionTransition = Promise . resolve ( ) ;
8592 player . session = loaded ( "source" ) ;
8693 player . operation = new PlayerOperation ( ) ;
@@ -89,7 +96,10 @@ function harness(
8996 player . playerState = {
9097 value : "playing" ,
9198 set : ( state ) => ( player . playerState . value = state ) ,
92- fail : ( error ) => failures . push ( error ) ,
99+ fail : ( error , positionMs ) => {
100+ failures . push ( error ) ;
101+ failurePositions . push ( positionMs ) ;
102+ } ,
93103 } ;
94104 player . deps = {
95105 loop : { stop : ( ) => undefined } ,
@@ -108,6 +118,7 @@ function harness(
108118 } ,
109119 } ;
110120 player . video = { paused, currentTime : 379.441 } ;
121+ player . recoveryPositionMs = 379_441 ;
111122 player . config = {
112123 endpoint : "https://beta.typetype.video/api" ,
113124 videoId : "nt1TGErpc0Q" ,
@@ -122,7 +133,7 @@ function harness(
122133 player . session = next ;
123134 return next ;
124135 } ;
125- return { player, requests, failures, qualities } ;
136+ return { player, requests, failures, failurePositions , qualities } ;
126137}
127138
128139function response ( sessionId : string ) : PlaybackResponse {
@@ -249,7 +260,7 @@ test("serializes media source session transitions", async () => {
249260} ) ;
250261
251262test ( "a generic failure aborts pending recovery and is reported once" , async ( ) => {
252- const { player, failures } = harness (
263+ const { player, failures, failurePositions } = harness (
253264 async ( _request , signal ) =>
254265 new Promise < PlaybackResponse > ( ( _resolve , reject ) => {
255266 signal ?. addEventListener (
@@ -264,6 +275,7 @@ test("a generic failure aborts pending recovery and is reported once", async ()
264275 signal : player . operation . signal ,
265276 } ) ;
266277 const generic = new Error ( "media append failed" ) ;
278+ player . video . currentTime = 0 ;
267279
268280 player . handlePlaybackLoopError ( generic , {
269281 sessionId : "source" ,
@@ -273,9 +285,22 @@ test("a generic failure aborts pending recovery and is reported once", async ()
273285 await Bun . sleep ( 0 ) ;
274286
275287 expect ( failures ) . toEqual ( [ generic ] ) ;
288+ expect ( failurePositions ) . toEqual ( [ 379_441 ] ) ;
276289 expect ( player . operation . signal . aborted ) . toBe ( true ) ;
277290} ) ;
278291
292+ test ( "an initial load failure reports its configured recovery position" , async ( ) => {
293+ const { player, failures, failurePositions } = harness ( async ( ) => response ( "unused" ) ) ;
294+ const failure = new Error ( "gateway unavailable" ) ;
295+ player . video . currentTime = 0 ;
296+ player . loadInitialSession = ( ) => Promise . reject ( failure ) ;
297+
298+ await expect ( player . load ( ) ) . rejects . toBe ( failure ) ;
299+
300+ expect ( failures ) . toEqual ( [ failure ] ) ;
301+ expect ( failurePositions ) . toEqual ( [ 379_441 ] ) ;
302+ } ) ;
303+
279304test ( "an old generic failure cannot replace an active recovery" , async ( ) => {
280305 let release : ( ( value : PlaybackResponse ) => void ) | null = null ;
281306 const pending = new Promise < PlaybackResponse > ( ( resolve ) => {
0 commit comments