livekit-client 2.18.8 → 2.18.9

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -603,237 +603,389 @@ describe('DataTrackOutgoingManager', () => {
603
603
  await shutdownPromise;
604
604
  });
605
605
 
606
- it('should resolve flush() after a single tryPush once the packet is acknowledged', async () => {
607
- const pubHandle = 5;
608
- const manager = OutgoingDataTrackManager.withDescriptors(
609
- new Map([
610
- [
611
- DataTrackHandle.fromNumber(pubHandle),
612
- Descriptor.active(
613
- {
614
- sid: 'bogus-sid',
615
- pubHandle,
616
- name: 'test',
617
- usesE2ee: false,
618
- },
619
- null,
620
- ),
621
- ],
622
- ]),
623
- );
624
- const managerEvents = subscribeToEvents<DataTrackOutgoingManagerCallbacks>(manager, [
625
- 'packetAvailable',
626
- 'packetsFlushed',
627
- ]);
628
- const localDataTrack = LocalDataTrack.withExplicitHandle(
629
- { name: 'track name' },
630
- manager,
631
- pubHandle,
632
- );
606
+ describe('localDataTrack.flush()', () => {
607
+ it('should resolve flush() after a single tryPush once the packet is acknowledged', async () => {
608
+ const pubHandle = 5;
609
+ const manager = OutgoingDataTrackManager.withDescriptors(
610
+ new Map([
611
+ [
612
+ DataTrackHandle.fromNumber(pubHandle),
613
+ Descriptor.active(
614
+ {
615
+ sid: 'bogus-sid',
616
+ pubHandle,
617
+ name: 'test',
618
+ usesE2ee: false,
619
+ },
620
+ null,
621
+ ),
622
+ ],
623
+ ]),
624
+ );
625
+ const managerEvents = subscribeToEvents<DataTrackOutgoingManagerCallbacks>(manager, [
626
+ 'packetAvailable',
627
+ 'packetsFlushedChange',
628
+ ]);
629
+ const localDataTrack = LocalDataTrack.withExplicitHandle(
630
+ { name: 'track name' },
631
+ manager,
632
+ pubHandle,
633
+ );
633
634
 
634
- // 1. Push a single-packet payload
635
- await localDataTrack.tryPush({ payload: new Uint8Array([0x01, 0x02, 0x03, 0x04, 0x05]) });
635
+ // 1. Push a single-packet payload
636
+ await localDataTrack.tryPush({ payload: new Uint8Array([0x01, 0x02, 0x03, 0x04, 0x05]) });
637
+
638
+ // 2. The packet should have been emitted to be sent over the data channel
639
+ const packetEvent = await managerEvents.waitFor('packetAvailable');
640
+ expect(packetEvent.handle).toStrictEqual(pubHandle);
641
+
642
+ // 3. A event should be sent indicating that the data track is no longer "flushed"
643
+ const noLongerFlushedEvent = await managerEvents.waitFor('packetsFlushedChange');
644
+ expect(noLongerFlushedEvent.handle).toStrictEqual(pubHandle);
645
+ expect(noLongerFlushedEvent.isFlushed).toStrictEqual(false);
646
+
647
+ // 3. Calling flush() right after tryPush() should not resolve until the packet
648
+ // is acknowledged via handlePacketSendComplete
649
+ let flushed = false;
650
+ const flushPromise = localDataTrack.flush().then(() => {
651
+ flushed = true;
652
+ });
653
+
654
+ await new Promise((resolve) => setTimeout(resolve, 0));
655
+ expect(flushed).toStrictEqual(false);
656
+ expect(managerEvents.areThereBufferedEvents('packetsFlushedChange')).toBe(false);
636
657
 
637
- // 2. The packet should have been emitted to be sent over the data channel
638
- const packetEvent = await managerEvents.waitFor('packetAvailable');
639
- expect(packetEvent.handle).toStrictEqual(pubHandle);
658
+ // 4. Acknowledge that the packet has been sent over the data channel
659
+ manager.handlePacketSendComplete(DataTrackHandle.fromNumber(pubHandle));
640
660
 
641
- // 3. Calling flush() right after tryPush() should not resolve until the packet
642
- // is acknowledged via handlePacketSendComplete
643
- let flushed = false;
644
- const flushPromise = localDataTrack.flush().then(() => {
645
- flushed = true;
661
+ // 5. The packetsFlushed event fires once the in-flight packet counter reaches 0
662
+ const flushedEvent = await managerEvents.waitFor('packetsFlushedChange');
663
+ expect(flushedEvent.handle).toStrictEqual(pubHandle);
664
+ expect(flushedEvent.isFlushed).toStrictEqual(true);
665
+
666
+ // 6. The flush() promise resolves
667
+ await flushPromise;
668
+ expect(flushed).toStrictEqual(true);
646
669
  });
647
670
 
648
- await new Promise((resolve) => setTimeout(resolve, 0));
649
- expect(flushed).toStrictEqual(false);
650
- expect(managerEvents.areThereBufferedEvents('packetsFlushed')).toBe(false);
671
+ it('should resolve flush() only after all packets in a multi-packet payload are acknowledged', async () => {
672
+ const pubHandle = 5;
673
+ const manager = OutgoingDataTrackManager.withDescriptors(
674
+ new Map([
675
+ [
676
+ DataTrackHandle.fromNumber(pubHandle),
677
+ Descriptor.active(
678
+ {
679
+ sid: 'bogus-sid',
680
+ pubHandle,
681
+ name: 'test',
682
+ usesE2ee: false,
683
+ },
684
+ null,
685
+ ),
686
+ ],
687
+ ]),
688
+ );
689
+ const managerEvents = subscribeToEvents<DataTrackOutgoingManagerCallbacks>(manager, [
690
+ 'packetAvailable',
691
+ 'packetsFlushedChange',
692
+ ]);
693
+ const localDataTrack = LocalDataTrack.withExplicitHandle(
694
+ { name: 'track name' },
695
+ manager,
696
+ pubHandle,
697
+ );
698
+
699
+ // 1. Push a payload large enough to span multiple packets (24k > single packet mtu)
700
+ await localDataTrack.tryPush({ payload: new Uint8Array(24_000).fill(0xbe) });
651
701
 
652
- // 4. Acknowledge that the packet has been sent over the data channel
653
- manager.handlePacketSendComplete(DataTrackHandle.fromNumber(pubHandle));
702
+ // 2. A event should be sent indicating that the data track is no longer "flushed"
703
+ const noLongerFlushedEvent = await managerEvents.waitFor('packetsFlushedChange');
704
+ expect(noLongerFlushedEvent.handle).toStrictEqual(pubHandle);
705
+ expect(noLongerFlushedEvent.isFlushed).toStrictEqual(false);
654
706
 
655
- // 5. The packetsFlushed event fires once the in-flight packet counter reaches 0
656
- const flushedEvent = await managerEvents.waitFor('packetsFlushed');
657
- expect(flushedEvent.handle).toStrictEqual(pubHandle);
707
+ // 3. Two packetAvailable events should be emitted for this payload
708
+ await managerEvents.waitFor('packetAvailable');
709
+ await managerEvents.waitFor('packetAvailable');
658
710
 
659
- // 6. The flush() promise resolves
660
- await flushPromise;
661
- expect(flushed).toStrictEqual(true);
662
- });
711
+ // 4. Call flush() before any of the packets have been acknowledged
712
+ let flushed = false;
713
+ const flushPromise = localDataTrack.flush().then(() => {
714
+ flushed = true;
715
+ });
663
716
 
664
- it('should resolve flush() only after all packets in a multi-packet payload are acknowledged', async () => {
665
- const pubHandle = 5;
666
- const manager = OutgoingDataTrackManager.withDescriptors(
667
- new Map([
668
- [
669
- DataTrackHandle.fromNumber(pubHandle),
670
- Descriptor.active(
671
- {
672
- sid: 'bogus-sid',
673
- pubHandle,
674
- name: 'test',
675
- usesE2ee: false,
676
- },
677
- null,
678
- ),
679
- ],
680
- ]),
681
- );
682
- const managerEvents = subscribeToEvents<DataTrackOutgoingManagerCallbacks>(manager, [
683
- 'packetAvailable',
684
- 'packetsFlushed',
685
- ]);
686
- const localDataTrack = LocalDataTrack.withExplicitHandle(
687
- { name: 'track name' },
688
- manager,
689
- pubHandle,
690
- );
717
+ // 5. Acknowledge the first packet -- flush should not resolve yet, in-flight counter still > 0
718
+ manager.handlePacketSendComplete(DataTrackHandle.fromNumber(pubHandle));
719
+ await new Promise((resolve) => setTimeout(resolve, 0));
720
+ expect(flushed).toStrictEqual(false);
721
+ expect(managerEvents.areThereBufferedEvents('packetsFlushedChange')).toBe(false);
691
722
 
692
- // 1. Push a payload large enough to span multiple packets (24k > single packet mtu)
693
- await localDataTrack.tryPush({ payload: new Uint8Array(24_000).fill(0xbe) });
723
+ // 6. Acknowledge the second packet -- flush resolves once the counter reaches 0
724
+ manager.handlePacketSendComplete(DataTrackHandle.fromNumber(pubHandle));
694
725
 
695
- // 2. Two packetAvailable events should be emitted for this payload
696
- await managerEvents.waitFor('packetAvailable');
697
- await managerEvents.waitFor('packetAvailable');
726
+ const flushedEvent = await managerEvents.waitFor('packetsFlushedChange');
727
+ expect(flushedEvent.handle).toStrictEqual(pubHandle);
728
+ expect(flushedEvent.isFlushed).toStrictEqual(true);
698
729
 
699
- // 3. Call flush() before any of the packets have been acknowledged
700
- let flushed = false;
701
- const flushPromise = localDataTrack.flush().then(() => {
702
- flushed = true;
730
+ await flushPromise;
731
+ expect(flushed).toStrictEqual(true);
703
732
  });
704
733
 
705
- // 4. Acknowledge the first packet -- flush should not resolve yet, in-flight counter still > 0
706
- manager.handlePacketSendComplete(DataTrackHandle.fromNumber(pubHandle));
707
- await new Promise((resolve) => setTimeout(resolve, 0));
708
- expect(flushed).toStrictEqual(false);
709
- expect(managerEvents.areThereBufferedEvents('packetsFlushed')).toBe(false);
734
+ it('should resolve any pending flush() calls when the manager is reset', async () => {
735
+ const pubHandle = 5;
736
+ const manager = OutgoingDataTrackManager.withDescriptors(
737
+ new Map([
738
+ [
739
+ DataTrackHandle.fromNumber(pubHandle),
740
+ Descriptor.active(
741
+ {
742
+ sid: 'bogus-sid',
743
+ pubHandle,
744
+ name: 'test',
745
+ usesE2ee: false,
746
+ },
747
+ null,
748
+ ),
749
+ ],
750
+ ]),
751
+ );
752
+ const managerEvents = subscribeToEvents<DataTrackOutgoingManagerCallbacks>(manager, [
753
+ 'packetAvailable',
754
+ 'packetsFlushedChange',
755
+ 'reset',
756
+ ]);
757
+ const localDataTrack = LocalDataTrack.withExplicitHandle(
758
+ { name: 'track name' },
759
+ manager,
760
+ pubHandle,
761
+ );
710
762
 
711
- // 5. Acknowledge the second packet -- flush resolves once the counter reaches 0
712
- manager.handlePacketSendComplete(DataTrackHandle.fromNumber(pubHandle));
763
+ // 1. Push a single-packet payload
764
+ await localDataTrack.tryPush({ payload: new Uint8Array([0x01, 0x02, 0x03, 0x04, 0x05]) });
713
765
 
714
- const flushedEvent = await managerEvents.waitFor('packetsFlushed');
715
- expect(flushedEvent.handle).toStrictEqual(pubHandle);
766
+ // 2. A event should be sent indicating that the data track is no longer "flushed"
767
+ const noLongerFlushedEvent = await managerEvents.waitFor('packetsFlushedChange');
768
+ expect(noLongerFlushedEvent.handle).toStrictEqual(pubHandle);
769
+ expect(noLongerFlushedEvent.isFlushed).toStrictEqual(false);
716
770
 
717
- await flushPromise;
718
- expect(flushed).toStrictEqual(true);
719
- });
771
+ await managerEvents.waitFor('packetAvailable');
720
772
 
721
- it('should resolve any pending flush() calls when the manager is reset', async () => {
722
- const pubHandle = 5;
723
- const manager = OutgoingDataTrackManager.withDescriptors(
724
- new Map([
725
- [
726
- DataTrackHandle.fromNumber(pubHandle),
727
- Descriptor.active(
728
- {
729
- sid: 'bogus-sid',
730
- pubHandle,
731
- name: 'test',
732
- usesE2ee: false,
733
- },
734
- null,
735
- ),
736
- ],
737
- ]),
738
- );
739
- const managerEvents = subscribeToEvents<DataTrackOutgoingManagerCallbacks>(manager, [
740
- 'packetAvailable',
741
- 'packetsFlushed',
742
- 'reset',
743
- ]);
744
- const localDataTrack = LocalDataTrack.withExplicitHandle(
745
- { name: 'track name' },
746
- manager,
747
- pubHandle,
748
- );
773
+ // 3. Call flush() before the in-flight packet is acknowledged -- it should remain
774
+ // pending because the in-flight counter is still > 0
775
+ let flushed = false;
776
+ const flushPromise = localDataTrack.flush().then(() => {
777
+ flushed = true;
778
+ });
749
779
 
750
- // 1. Push a single-packet payload
751
- await localDataTrack.tryPush({ payload: new Uint8Array([0x01, 0x02, 0x03, 0x04, 0x05]) });
752
- await managerEvents.waitFor('packetAvailable');
780
+ await new Promise((resolve) => setTimeout(resolve, 0));
781
+ expect(flushed).toStrictEqual(false);
782
+ expect(managerEvents.areThereBufferedEvents('packetsFlushedChange')).toBe(false);
783
+
784
+ // 4. Reset the manager. This simulates a RTCEngine disconnect and should resolve
785
+ // the pending flush() even though the packet was never acknowledged.
786
+ await manager.reset();
787
+ await managerEvents.waitFor('reset');
788
+
789
+ // 5. The flush() promise resolves
790
+ await flushPromise;
791
+ expect(flushed).toStrictEqual(true);
753
792
 
754
- // 2. Call flush() before the in-flight packet is acknowledged -- it should remain
755
- // pending because the in-flight counter is still > 0
756
- let flushed = false;
757
- const flushPromise = localDataTrack.flush().then(() => {
758
- flushed = true;
793
+ // 6. No packetsFlushed event was emitted -- reset short-circuits the flush directly
794
+ // on the LocalDataTrack rather than going through the in-flight counter.
795
+ expect(managerEvents.areThereBufferedEvents('packetsFlushedChange')).toBe(false);
759
796
  });
760
797
 
761
- await new Promise((resolve) => setTimeout(resolve, 0));
762
- expect(flushed).toStrictEqual(false);
763
- expect(managerEvents.areThereBufferedEvents('packetsFlushed')).toBe(false);
798
+ it('should resolve flush() at the end of a batch of tryPush calls after all packets are later acknowledged as "sent"', async () => {
799
+ const pubHandle = 5;
800
+ const manager = OutgoingDataTrackManager.withDescriptors(
801
+ new Map([
802
+ [
803
+ DataTrackHandle.fromNumber(pubHandle),
804
+ Descriptor.active(
805
+ {
806
+ sid: 'bogus-sid',
807
+ pubHandle,
808
+ name: 'test',
809
+ usesE2ee: false,
810
+ },
811
+ null,
812
+ ),
813
+ ],
814
+ ]),
815
+ );
816
+ const managerEvents = subscribeToEvents<DataTrackOutgoingManagerCallbacks>(manager, [
817
+ 'packetAvailable',
818
+ 'packetsFlushedChange',
819
+ ]);
820
+ const localDataTrack = LocalDataTrack.withExplicitHandle(
821
+ { name: 'track name' },
822
+ manager,
823
+ pubHandle,
824
+ );
764
825
 
765
- // 3. Reset the manager. This simulates a RTCEngine disconnect and should resolve
766
- // the pending flush() even though the packet was never acknowledged.
767
- await manager.reset();
768
- await managerEvents.waitFor('reset');
826
+ // 1. Run a batch of tryPush calls
827
+ await localDataTrack.tryPush({ payload: new Uint8Array([0x01]) });
828
+ await localDataTrack.tryPush({ payload: new Uint8Array([0x02]) });
829
+ await localDataTrack.tryPush({ payload: new Uint8Array([0x03]) });
830
+
831
+ // 2. A event should have been sent indicating that the data track is no longer "flushed"
832
+ const noLongerFlushedEvent = await managerEvents.waitFor('packetsFlushedChange');
833
+ expect(noLongerFlushedEvent.handle).toStrictEqual(pubHandle);
834
+ expect(noLongerFlushedEvent.isFlushed).toStrictEqual(false);
835
+
836
+ // 3. Three packetAvailable events should be emitted, one per pushed frame
837
+ await managerEvents.waitFor('packetAvailable');
838
+ await managerEvents.waitFor('packetAvailable');
839
+ await managerEvents.waitFor('packetAvailable');
840
+
841
+ // 4. After the batch is enqueued, call flush() to wait for the SFU to drain them
842
+ let flushed = false;
843
+ const flushPromise = localDataTrack.flush().then(() => {
844
+ flushed = true;
845
+ });
846
+
847
+ // 5. Acknowledge two of the three packets -- flush should not resolve yet
848
+ manager.handlePacketSendComplete(DataTrackHandle.fromNumber(pubHandle));
849
+ manager.handlePacketSendComplete(DataTrackHandle.fromNumber(pubHandle));
850
+ await new Promise((resolve) => setTimeout(resolve, 0));
851
+ expect(flushed).toStrictEqual(false);
852
+ expect(managerEvents.areThereBufferedEvents('packetsFlushedChange')).toBe(false);
853
+
854
+ // 6. Acknowledge the last packet -- flush resolves once the counter reaches 0
855
+ manager.handlePacketSendComplete(DataTrackHandle.fromNumber(pubHandle));
856
+
857
+ const flushedEvent = await managerEvents.waitFor('packetsFlushedChange');
858
+ expect(flushedEvent.handle).toStrictEqual(pubHandle);
859
+
860
+ await flushPromise;
861
+ expect(flushed).toStrictEqual(true);
862
+ });
769
863
 
770
- // 4. The flush() promise resolves
771
- await flushPromise;
772
- expect(flushed).toStrictEqual(true);
864
+ it('should resolve flush() if there are no tryPush calls in flight', async () => {
865
+ const pubHandle = 5;
866
+ const manager = OutgoingDataTrackManager.withDescriptors(
867
+ new Map([
868
+ [
869
+ DataTrackHandle.fromNumber(pubHandle),
870
+ Descriptor.active(
871
+ {
872
+ sid: 'bogus-sid',
873
+ pubHandle,
874
+ name: 'test',
875
+ usesE2ee: false,
876
+ },
877
+ null,
878
+ ),
879
+ ],
880
+ ]),
881
+ );
882
+ const localDataTrack = LocalDataTrack.withExplicitHandle(
883
+ { name: 'track name' },
884
+ manager,
885
+ pubHandle,
886
+ );
773
887
 
774
- // 5. No packetsFlushed event was emitted -- reset short-circuits the flush directly
775
- // on the LocalDataTrack rather than going through the in-flight counter.
776
- expect(managerEvents.areThereBufferedEvents('packetsFlushed')).toBe(false);
777
- });
888
+ // Call flush, make sure it resolves on its own
889
+ await localDataTrack.flush();
890
+ });
778
891
 
779
- it('should resolve flush() at the end of a batch of tryPush calls once all packets are acknowledged', async () => {
780
- const pubHandle = 5;
781
- const manager = OutgoingDataTrackManager.withDescriptors(
782
- new Map([
783
- [
784
- DataTrackHandle.fromNumber(pubHandle),
785
- Descriptor.active(
786
- {
787
- sid: 'bogus-sid',
788
- pubHandle,
789
- name: 'test',
790
- usesE2ee: false,
791
- },
792
- null,
793
- ),
794
- ],
795
- ]),
796
- );
797
- const managerEvents = subscribeToEvents<DataTrackOutgoingManagerCallbacks>(manager, [
798
- 'packetAvailable',
799
- 'packetsFlushed',
800
- ]);
801
- const localDataTrack = LocalDataTrack.withExplicitHandle(
802
- { name: 'track name' },
803
- manager,
804
- pubHandle,
805
- );
892
+ it('should resolve flush() at the end of a batch of tryPush calls which have all fully sent their data', async () => {
893
+ const pubHandle = 5;
894
+ const manager = OutgoingDataTrackManager.withDescriptors(
895
+ new Map([
896
+ [
897
+ DataTrackHandle.fromNumber(pubHandle),
898
+ Descriptor.active(
899
+ {
900
+ sid: 'bogus-sid',
901
+ pubHandle,
902
+ name: 'test',
903
+ usesE2ee: false,
904
+ },
905
+ null,
906
+ ),
907
+ ],
908
+ ]),
909
+ );
910
+ const managerEvents = subscribeToEvents<DataTrackOutgoingManagerCallbacks>(manager, [
911
+ 'packetAvailable',
912
+ ]);
913
+ const localDataTrack = LocalDataTrack.withExplicitHandle(
914
+ { name: 'track name' },
915
+ manager,
916
+ pubHandle,
917
+ );
806
918
 
807
- // 1. Run a batch of tryPush calls
808
- await localDataTrack.tryPush({ payload: new Uint8Array([0x01]) });
809
- await localDataTrack.tryPush({ payload: new Uint8Array([0x02]) });
810
- await localDataTrack.tryPush({ payload: new Uint8Array([0x03]) });
919
+ // 1. Run a batch of tryPush calls
920
+ await localDataTrack.tryPush({ payload: new Uint8Array([0x01]) });
921
+ await localDataTrack.tryPush({ payload: new Uint8Array([0x02]) });
922
+ await localDataTrack.tryPush({ payload: new Uint8Array([0x03]) });
811
923
 
812
- // 2. Three packetAvailable events should be emitted, one per pushed frame
813
- await managerEvents.waitFor('packetAvailable');
814
- await managerEvents.waitFor('packetAvailable');
815
- await managerEvents.waitFor('packetAvailable');
924
+ // 2. Three packetAvailable events should be emitted, one per pushed frame
925
+ await managerEvents.waitFor('packetAvailable');
926
+ await managerEvents.waitFor('packetAvailable');
927
+ await managerEvents.waitFor('packetAvailable');
928
+
929
+ // 3. Acknowledge all three packets
930
+ manager.handlePacketSendComplete(DataTrackHandle.fromNumber(pubHandle));
931
+ manager.handlePacketSendComplete(DataTrackHandle.fromNumber(pubHandle));
932
+ manager.handlePacketSendComplete(DataTrackHandle.fromNumber(pubHandle));
816
933
 
817
- // 3. After the batch is enqueued, call flush() to wait for the SFU to drain them
818
- let flushed = false;
819
- const flushPromise = localDataTrack.flush().then(() => {
820
- flushed = true;
934
+ // 4. Call flush and ensure that it resolves immediately
935
+ await localDataTrack.flush();
821
936
  });
822
937
 
823
- // 4. Acknowledge two of the three packets -- flush should not resolve yet
824
- manager.handlePacketSendComplete(DataTrackHandle.fromNumber(pubHandle));
825
- manager.handlePacketSendComplete(DataTrackHandle.fromNumber(pubHandle));
826
- await new Promise((resolve) => setTimeout(resolve, 0));
827
- expect(flushed).toStrictEqual(false);
828
- expect(managerEvents.areThereBufferedEvents('packetsFlushed')).toBe(false);
938
+ it('should send packetsFlushedChange events in between tryPush / handlePacketSendComplete calls', async () => {
939
+ const pubHandle = 5;
940
+ const manager = OutgoingDataTrackManager.withDescriptors(
941
+ new Map([
942
+ [
943
+ DataTrackHandle.fromNumber(pubHandle),
944
+ Descriptor.active(
945
+ {
946
+ sid: 'bogus-sid',
947
+ pubHandle,
948
+ name: 'test',
949
+ usesE2ee: false,
950
+ },
951
+ null,
952
+ ),
953
+ ],
954
+ ]),
955
+ );
956
+ const managerEvents = subscribeToEvents<DataTrackOutgoingManagerCallbacks>(manager, [
957
+ 'packetsFlushedChange',
958
+ ]);
959
+ const localDataTrack = LocalDataTrack.withExplicitHandle(
960
+ { name: 'track name' },
961
+ manager,
962
+ pubHandle,
963
+ );
964
+
965
+ // 1. Send a single packet frame
966
+ await localDataTrack.tryPush({ payload: new Uint8Array([0x01]) });
829
967
 
830
- // 5. Acknowledge the last packet -- flush resolves once the counter reaches 0
831
- manager.handlePacketSendComplete(DataTrackHandle.fromNumber(pubHandle));
968
+ // 2. Ensure the data track is no longer "flushed"
969
+ const noLongerFlushedEvent = await managerEvents.waitFor('packetsFlushedChange');
970
+ expect(noLongerFlushedEvent.handle).toStrictEqual(pubHandle);
971
+ expect(noLongerFlushedEvent.isFlushed).toStrictEqual(false);
832
972
 
833
- const flushedEvent = await managerEvents.waitFor('packetsFlushed');
834
- expect(flushedEvent.handle).toStrictEqual(pubHandle);
973
+ // 3. Send more single packet frames
974
+ await localDataTrack.tryPush({ payload: new Uint8Array([0x02]) });
975
+ await localDataTrack.tryPush({ payload: new Uint8Array([0x03]) });
835
976
 
836
- await flushPromise;
837
- expect(flushed).toStrictEqual(true);
977
+ // 3. Acknowledge the first two packets
978
+ manager.handlePacketSendComplete(DataTrackHandle.fromNumber(pubHandle));
979
+ manager.handlePacketSendComplete(DataTrackHandle.fromNumber(pubHandle));
980
+ expect(managerEvents.areThereBufferedEvents('packetsFlushedChange')).toBe(false);
981
+
982
+ // 4. Acknowledge the last packet
983
+ manager.handlePacketSendComplete(DataTrackHandle.fromNumber(pubHandle));
984
+
985
+ // 4. The data track should now be "flushed" again
986
+ const isFlushedEvent = await managerEvents.waitFor('packetsFlushedChange');
987
+ expect(isFlushedEvent.handle).toStrictEqual(pubHandle);
988
+ expect(isFlushedEvent.isFlushed).toStrictEqual(true);
989
+ });
838
990
  });
839
991
  });
@@ -18,7 +18,7 @@ import DataTrackOutgoingPipeline from './pipeline';
18
18
  import {
19
19
  type DataTrackOptions,
20
20
  type EventPacketAvailable,
21
- type EventPacketsFlushed,
21
+ type EventPacketsFlushedChange,
22
22
  type EventSfuPublishRequest,
23
23
  type EventSfuUnpublishRequest,
24
24
  type EventTrackPublished,
@@ -76,7 +76,7 @@ export type DataTrackOutgoingManagerCallbacks = {
76
76
  /** A {@link LocalDataTrack} has been unpublished */
77
77
  trackUnpublished: (event: EventTrackUnpublished) => void;
78
78
  /** A {@link LocalDataTrack} has had all of its in flight packets sent via the rtc data channel. */
79
- packetsFlushed: (event: EventPacketsFlushed) => void;
79
+ packetsFlushedChange: (event: EventPacketsFlushedChange) => void;
80
80
  /** The manager has been reset and all state has been cleared in preparation for the next room
81
81
  * connection. */
82
82
  reset: () => void;
@@ -167,6 +167,12 @@ export default class OutgoingDataTrackManager extends (EventEmitter as new () =>
167
167
  for await (const packet of descriptor.pipeline.processFrame(frame)) {
168
168
  const prev = this.inFlightPacketCounter.get(handle) ?? 0;
169
169
  this.inFlightPacketCounter.set(handle, prev + 1);
170
+ if (prev === 0) {
171
+ // A new packet has been sent, so there are now packets in the rtc data channel buffer for
172
+ // this data track frame
173
+ this.emit('packetsFlushedChange', { handle, isFlushed: false });
174
+ }
175
+
170
176
  this.emit('packetAvailable', { handle, bytes: packet.toBinary() });
171
177
  }
172
178
  } catch (err) {
@@ -193,7 +199,7 @@ export default class OutgoingDataTrackManager extends (EventEmitter as new () =>
193
199
  this.inFlightPacketCounter.set(handle, counter);
194
200
 
195
201
  if (counter === 0) {
196
- this.emit('packetsFlushed', { handle });
202
+ this.emit('packetsFlushedChange', { handle, isFlushed: true });
197
203
  }
198
204
  }
199
205
 
@@ -47,4 +47,4 @@ export type EventTrackPublished = { track: LocalDataTrack };
47
47
  export type EventTrackUnpublished = { sid: DataTrackSid };
48
48
 
49
49
  /** A track has had all of its in flight packets sent via the rtc data channel. */
50
- export type EventPacketsFlushed = { handle: DataTrackHandle };
50
+ export type EventPacketsFlushedChange = { handle: DataTrackHandle; isFlushed: boolean };