Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changeset/datatrack-republish-rejected.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"livekit-client": patch
---

Mark data track unpublished after republish fail
42 changes: 41 additions & 1 deletion src/room/data-track/outgoing/OutgoingDataTrackManager.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ import OutgoingDataTrackManager, {
type DataTrackOutgoingManagerCallbacks,
Descriptor,
} from './OutgoingDataTrackManager';
import { DataTrackPublishError } from './errors';
import { DataTrackPublishError, DataTrackPushFrameErrorReason } from './errors';

/** Fake encryption provider for testing e2ee data track features. */
export class PrefixingEncryptionProvider implements BaseE2EEManager {
Expand Down Expand Up @@ -521,6 +521,46 @@ describe('DataTrackOutgoingManager', () => {
await managerEvents.waitFor('packetAvailable');
});

it('should unpublish a track when republish is rejected', async () => {
const manager = new OutgoingDataTrackManager();
const managerEvents = subscribeToEvents<DataTrackOutgoingManagerCallbacks>(manager, [
'sfuPublishRequest',
]);

const localDataTrack = new LocalDataTrack({ name: 'track' }, manager);
const publishRequestPromise = localDataTrack.publish();

const sfuPublishEvent = await managerEvents.waitFor('sfuPublishRequest');
const handle = sfuPublishEvent.handle;

manager.receivedSfuPublishResponse(handle, {
type: 'ok',
data: {
sid: 'bogus-sid',
pubHandle: handle,
name: 'track',
usesE2ee: false,
},
});
await publishRequestPromise;

// Simulate reconnect
manager.sfuWillRepublishTracks();
await managerEvents.waitFor('sfuPublishRequest');

// SFU rejects the republish
manager.receivedSfuPublishResponse(handle, {
type: 'error',
error: DataTrackPublishError.notAllowed(),
});

// Track is unpublished
expect(localDataTrack.isPublished()).toStrictEqual(false);
await expect(() =>
localDataTrack.tryPush({ payload: new Uint8Array([0xff]) }),
).rejects.toMatchObject({ reason: DataTrackPushFrameErrorReason.TrackUnpublished });
});

it('should query currently active descriptors', async () => {
// Create a manager prefilled with a descriptor
const manager = OutgoingDataTrackManager.withDescriptors(
Expand Down
4 changes: 3 additions & 1 deletion src/room/data-track/outgoing/OutgoingDataTrackManager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -343,7 +343,8 @@ export default class OutgoingDataTrackManager extends (EventEmitter as new () =>
return;
}
if (result.type === 'error') {
log.warn(`Republish failed for track ${handle}`);
log.warn(`Republish failed for track ${handle}: ${result.error}`);
descriptor.publishState = 'unpublished';
return;
}

Expand Down Expand Up @@ -380,6 +381,7 @@ export default class OutgoingDataTrackManager extends (EventEmitter as new () =>
*
* This must be sent after a full reconnect in order for existing publications
* to be recognized by the SFU. Each republished track will be assigned a new SID.
* Tracks the SFU rejects are unpublished.
* @internal
*/
sfuWillRepublishTracks() {
Expand Down
Loading