@maravilla-labs/platform 0.1.27 → 0.1.29

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.
package/dist/index.js CHANGED
@@ -1,3 +1,55 @@
1
+ // src/media.ts
2
+ var RemoteMediaService = class {
3
+ constructor(baseUrl, headers) {
4
+ this.baseUrl = baseUrl;
5
+ this.headers = headers;
6
+ }
7
+ baseUrl;
8
+ headers;
9
+ async fetch(url, options = {}) {
10
+ const response = await fetch(url, {
11
+ ...options,
12
+ headers: { ...this.headers, ...options.headers }
13
+ });
14
+ if (!response.ok) {
15
+ const error = await response.text();
16
+ throw new Error(`Media API error: ${response.status} - ${error}`);
17
+ }
18
+ return response;
19
+ }
20
+ async createRoom(roomId, settings = {}) {
21
+ const response = await this.fetch(`${this.baseUrl}/api/media/rooms`, {
22
+ method: "POST",
23
+ body: JSON.stringify({ roomId, settings })
24
+ });
25
+ return response.json();
26
+ }
27
+ async deleteRoom(roomId) {
28
+ await this.fetch(`${this.baseUrl}/api/media/rooms/${encodeURIComponent(roomId)}`, {
29
+ method: "DELETE"
30
+ });
31
+ }
32
+ async listRooms() {
33
+ const response = await this.fetch(`${this.baseUrl}/api/media/rooms`);
34
+ return response.json();
35
+ }
36
+ async generateToken(roomId, participant) {
37
+ const response = await this.fetch(
38
+ `${this.baseUrl}/api/media/rooms/${encodeURIComponent(roomId)}/token`,
39
+ {
40
+ method: "POST",
41
+ body: JSON.stringify(participant)
42
+ }
43
+ );
44
+ return response.json();
45
+ }
46
+ async mediaUrl() {
47
+ const response = await this.fetch(`${this.baseUrl}/api/media/url`);
48
+ const data = await response.json();
49
+ return data.url;
50
+ }
51
+ };
52
+
1
53
  // src/remote-client.ts
2
54
  var RemoteKvNamespace = class {
3
55
  constructor(baseUrl, namespace, headers) {
@@ -5,6 +57,9 @@ var RemoteKvNamespace = class {
5
57
  this.namespace = namespace;
6
58
  this.headers = headers;
7
59
  }
60
+ baseUrl;
61
+ namespace;
62
+ headers;
8
63
  /**
9
64
  * Internal method for making HTTP requests to the dev server.
10
65
  * Handles error responses and authentication headers.
@@ -61,6 +116,8 @@ var RemoteDatabase = class {
61
116
  this.baseUrl = baseUrl;
62
117
  this.headers = headers;
63
118
  }
119
+ baseUrl;
120
+ headers;
64
121
  /**
65
122
  * Internal method for making HTTP requests to the dev server.
66
123
  * Handles error responses and authentication headers.
@@ -124,6 +181,8 @@ var RemoteStorage = class _RemoteStorage {
124
181
  this.baseUrl = baseUrl;
125
182
  this.headers = headers;
126
183
  }
184
+ baseUrl;
185
+ headers;
127
186
  /**
128
187
  * Internal method for making HTTP requests to the dev server.
129
188
  * Handles error responses and authentication headers.
@@ -276,12 +335,14 @@ function createRemoteClient(baseUrl, tenant) {
276
335
  });
277
336
  const db = new RemoteDatabase(baseUrl, headers);
278
337
  const storage = new RemoteStorage(baseUrl, headers);
338
+ const media = new RemoteMediaService(baseUrl, headers);
279
339
  return {
280
340
  env: {
281
341
  KV: kvProxy,
282
342
  DB: db,
283
343
  STORAGE: storage
284
- }
344
+ },
345
+ media
285
346
  };
286
347
  }
287
348
 
@@ -317,10 +378,13 @@ var RenClient = class {
317
378
  console.log("[REN detectEndpoint] Detected production runtime, using relative endpoint");
318
379
  return "/api/maravilla/ren";
319
380
  }
320
- if (typeof window !== "undefined" && window.location.port === "5173") {
321
- const devServerUrl = `http://${window.location.hostname}:3001`;
322
- console.log("[REN detectEndpoint] Detected Vite dev server (port 5173), using dev server:", devServerUrl);
323
- return `${devServerUrl}/api/maravilla/ren`;
381
+ if (typeof window !== "undefined") {
382
+ const port = window.location.port;
383
+ if (port && port !== "3001" && port !== "80" && port !== "443") {
384
+ const devServerUrl = `http://${window.location.hostname}:3001`;
385
+ console.log("[REN detectEndpoint] Detected dev mode (port " + port + "), using dev server:", devServerUrl);
386
+ return `${devServerUrl}/api/maravilla/ren`;
387
+ }
324
388
  }
325
389
  if (typeof globalThis !== "undefined" && globalThis.__maravilla_platform) {
326
390
  let devServerUrl = "http://localhost:3001";
@@ -373,6 +437,12 @@ var RenClient = class {
373
437
  "runtime.snapshot.ready",
374
438
  "runtime.worker.started",
375
439
  "runtime.worker.stopped",
440
+ // Realtime channel events
441
+ "realtime.message",
442
+ // Presence events
443
+ "presence.join",
444
+ "presence.leave",
445
+ "presence.update",
376
446
  // Meta events
377
447
  "ren.meta"
378
448
  ];
@@ -450,6 +520,468 @@ async function storageDelete(path, clientId) {
450
520
  return res.json().catch(() => ({}));
451
521
  }
452
522
 
523
+ // src/realtime.ts
524
+ var RealtimeClient = class {
525
+ wsEndpoint;
526
+ clientId;
527
+ ws = null;
528
+ closed = false;
529
+ attempt = 0;
530
+ autoReconnect;
531
+ maxBackoff;
532
+ debug;
533
+ channelListeners = /* @__PURE__ */ new Map();
534
+ globalListeners = /* @__PURE__ */ new Set();
535
+ presenceListeners = /* @__PURE__ */ new Map();
536
+ subscribedChannels = /* @__PURE__ */ new Set();
537
+ pendingMessages = [];
538
+ constructor(opts = {}) {
539
+ this.wsEndpoint = opts.wsEndpoint || this.detectEndpoint();
540
+ this.clientId = opts.clientId || getOrCreateClientId();
541
+ this.autoReconnect = opts.autoReconnect !== false;
542
+ this.maxBackoff = opts.maxBackoffMs ?? 15e3;
543
+ this.debug = !!opts.debug;
544
+ }
545
+ detectEndpoint() {
546
+ if (typeof window !== "undefined") {
547
+ const port = window.location.port;
548
+ const proto = window.location.protocol === "https:" ? "wss:" : "ws:";
549
+ if (port && port !== "3001" && port !== "80" && port !== "443") {
550
+ return `ws://${window.location.hostname}:3001/_rt/ws`;
551
+ }
552
+ return `${proto}//${window.location.host}/_rt/ws`;
553
+ }
554
+ return "ws://localhost:3001/_rt/ws";
555
+ }
556
+ log(...args) {
557
+ if (this.debug) console.debug("[RealtimeClient]", ...args);
558
+ }
559
+ /** Connect to the realtime WebSocket server */
560
+ connect() {
561
+ if (this.ws && this.ws.readyState === WebSocket.OPEN) return;
562
+ const url = `${this.wsEndpoint}?cid=${encodeURIComponent(this.clientId)}`;
563
+ this.log("connecting", url);
564
+ this.closed = false;
565
+ try {
566
+ this.ws = new WebSocket(url);
567
+ } catch (e) {
568
+ this.log("WebSocket constructor failed", e);
569
+ this.scheduleReconnect();
570
+ return;
571
+ }
572
+ this.ws.onopen = () => {
573
+ this.attempt = 0;
574
+ this.log("connected");
575
+ for (const ch of this.subscribedChannels) {
576
+ this.sendRaw({ action: "subscribe", channel: ch });
577
+ }
578
+ for (const msg of this.pendingMessages) {
579
+ this.ws?.send(msg);
580
+ }
581
+ this.pendingMessages = [];
582
+ };
583
+ this.ws.onmessage = (ev) => {
584
+ try {
585
+ const event = JSON.parse(ev.data);
586
+ this.log("received", event);
587
+ this.dispatch(event);
588
+ } catch (e) {
589
+ this.log("malformed message", ev.data, e);
590
+ }
591
+ };
592
+ this.ws.onerror = (ev) => {
593
+ this.log("error", ev);
594
+ };
595
+ this.ws.onclose = () => {
596
+ this.log("disconnected");
597
+ this.ws = null;
598
+ if (!this.closed && this.autoReconnect) {
599
+ this.scheduleReconnect();
600
+ }
601
+ };
602
+ }
603
+ scheduleReconnect() {
604
+ const delay = Math.min(1e3 * Math.pow(2, this.attempt++), this.maxBackoff);
605
+ this.log("reconnecting in", delay, "ms");
606
+ setTimeout(() => this.connect(), delay);
607
+ }
608
+ sendRaw(msg) {
609
+ const json = JSON.stringify(msg);
610
+ if (this.ws && this.ws.readyState === WebSocket.OPEN) {
611
+ this.ws.send(json);
612
+ } else {
613
+ this.pendingMessages.push(json);
614
+ }
615
+ }
616
+ dispatch(event) {
617
+ this.globalListeners.forEach((cb) => cb(event));
618
+ if (event.channel) {
619
+ const listeners = this.channelListeners.get(event.channel);
620
+ if (listeners) {
621
+ listeners.forEach((cb) => cb(event));
622
+ }
623
+ }
624
+ if (event.event === "presence:join" || event.event === "presence:leave") {
625
+ const presenceSet = this.presenceListeners.get(event.channel);
626
+ if (presenceSet) {
627
+ const member = {
628
+ userId: event.userId || "",
629
+ metadata: event.metadata,
630
+ lastSeen: event.ts
631
+ };
632
+ if (event.event === "presence:join") {
633
+ presenceSet.onJoin.forEach((cb) => cb(member));
634
+ } else {
635
+ presenceSet.onLeave.forEach((cb) => cb(member));
636
+ }
637
+ }
638
+ }
639
+ }
640
+ /** Subscribe to messages on a channel */
641
+ subscribe(channel, callback, options) {
642
+ if (!this.channelListeners.has(channel)) {
643
+ this.channelListeners.set(channel, /* @__PURE__ */ new Set());
644
+ }
645
+ this.channelListeners.get(channel).add(callback);
646
+ if (!this.subscribedChannels.has(channel)) {
647
+ this.subscribedChannels.add(channel);
648
+ const msg = { action: "subscribe", channel };
649
+ if (options?.token) {
650
+ msg.token = options.token;
651
+ }
652
+ this.sendRaw(msg);
653
+ }
654
+ return () => {
655
+ const listeners = this.channelListeners.get(channel);
656
+ if (listeners) {
657
+ listeners.delete(callback);
658
+ if (listeners.size === 0) {
659
+ this.channelListeners.delete(channel);
660
+ this.subscribedChannels.delete(channel);
661
+ this.sendRaw({ action: "unsubscribe", channel });
662
+ }
663
+ }
664
+ };
665
+ }
666
+ /** Listen to all events across all channels */
667
+ onAny(callback) {
668
+ this.globalListeners.add(callback);
669
+ return () => {
670
+ this.globalListeners.delete(callback);
671
+ };
672
+ }
673
+ /** Publish a message to a channel */
674
+ publish(channel, data, options) {
675
+ this.sendRaw({
676
+ action: "publish",
677
+ channel,
678
+ data,
679
+ userId: options?.userId
680
+ });
681
+ }
682
+ /** Get a presence handle for a channel */
683
+ presence(channel) {
684
+ if (!this.presenceListeners.has(channel)) {
685
+ this.presenceListeners.set(channel, {
686
+ onJoin: /* @__PURE__ */ new Set(),
687
+ onLeave: /* @__PURE__ */ new Set()
688
+ });
689
+ }
690
+ const listeners = this.presenceListeners.get(channel);
691
+ return {
692
+ /** Join the channel with presence */
693
+ join: (userId, metadata) => {
694
+ this.sendRaw({
695
+ action: "presence:join",
696
+ channel,
697
+ userId,
698
+ metadata
699
+ });
700
+ },
701
+ /** Leave the channel */
702
+ leave: () => {
703
+ this.sendRaw({ action: "presence:leave", channel });
704
+ },
705
+ /** Listen for users joining */
706
+ onJoin: (callback) => {
707
+ listeners.onJoin.add(callback);
708
+ return () => {
709
+ listeners.onJoin.delete(callback);
710
+ };
711
+ },
712
+ /** Listen for users leaving */
713
+ onLeave: (callback) => {
714
+ listeners.onLeave.add(callback);
715
+ return () => {
716
+ listeners.onLeave.delete(callback);
717
+ };
718
+ }
719
+ };
720
+ }
721
+ /** Get current client ID */
722
+ getClientId() {
723
+ return this.clientId;
724
+ }
725
+ /** Check if connected */
726
+ isConnected() {
727
+ return this.ws?.readyState === WebSocket.OPEN;
728
+ }
729
+ /** Disconnect and stop reconnecting */
730
+ disconnect() {
731
+ this.closed = true;
732
+ this.ws?.close();
733
+ this.ws = null;
734
+ this.pendingMessages = [];
735
+ this.log("disconnected (manual)");
736
+ }
737
+ };
738
+
739
+ // src/media-room.ts
740
+ import {
741
+ Room,
742
+ RoomEvent,
743
+ Track,
744
+ ConnectionState
745
+ } from "livekit-client";
746
+ var MediaRoomEvent = /* @__PURE__ */ ((MediaRoomEvent2) => {
747
+ MediaRoomEvent2["Connected"] = "connected";
748
+ MediaRoomEvent2["Reconnecting"] = "reconnecting";
749
+ MediaRoomEvent2["Reconnected"] = "reconnected";
750
+ MediaRoomEvent2["Disconnected"] = "disconnected";
751
+ MediaRoomEvent2["ParticipantJoined"] = "participantJoined";
752
+ MediaRoomEvent2["ParticipantLeft"] = "participantLeft";
753
+ MediaRoomEvent2["TrackSubscribed"] = "trackSubscribed";
754
+ MediaRoomEvent2["TrackUnsubscribed"] = "trackUnsubscribed";
755
+ MediaRoomEvent2["TrackMuted"] = "trackMuted";
756
+ MediaRoomEvent2["TrackUnmuted"] = "trackUnmuted";
757
+ MediaRoomEvent2["ActiveSpeakersChanged"] = "activeSpeakersChanged";
758
+ MediaRoomEvent2["DataReceived"] = "dataReceived";
759
+ MediaRoomEvent2["RecordingStatusChanged"] = "recordingStatusChanged";
760
+ MediaRoomEvent2["MediaDevicesChanged"] = "mediaDevicesChanged";
761
+ return MediaRoomEvent2;
762
+ })(MediaRoomEvent || {});
763
+ function mapSource(source) {
764
+ switch (source) {
765
+ case Track.Source.Camera:
766
+ return "camera";
767
+ case Track.Source.Microphone:
768
+ return "microphone";
769
+ case Track.Source.ScreenShare:
770
+ return "screen_share";
771
+ case Track.Source.ScreenShareAudio:
772
+ return "screen_share_audio";
773
+ default:
774
+ return "camera";
775
+ }
776
+ }
777
+ function mapParticipant(p) {
778
+ const tracks = [];
779
+ p.trackPublications.forEach((pub) => {
780
+ tracks.push({
781
+ trackSid: pub.trackSid,
782
+ source: mapSource(pub.source),
783
+ kind: pub.kind === Track.Kind.Video ? "video" : "audio",
784
+ muted: pub.isMuted,
785
+ track: pub.track?.mediaStreamTrack
786
+ });
787
+ });
788
+ return {
789
+ identity: p.identity,
790
+ name: p.name,
791
+ metadata: p.metadata,
792
+ isSpeaking: p.isSpeaking,
793
+ audioLevel: p.audioLevel,
794
+ tracks
795
+ };
796
+ }
797
+ function attachTrack(track, element) {
798
+ const stream = new MediaStream([track]);
799
+ element.srcObject = stream;
800
+ if (element instanceof HTMLVideoElement) {
801
+ element.playsInline = true;
802
+ }
803
+ element.play().catch(() => {
804
+ });
805
+ }
806
+ function detachTrack(element) {
807
+ element.srcObject = null;
808
+ }
809
+ var MediaLocalParticipant = class {
810
+ lp;
811
+ /** @internal */
812
+ constructor(lp) {
813
+ this.lp = lp;
814
+ }
815
+ get identity() {
816
+ return this.lp.identity;
817
+ }
818
+ get name() {
819
+ return this.lp.name;
820
+ }
821
+ get metadata() {
822
+ return this.lp.metadata;
823
+ }
824
+ get isSpeaking() {
825
+ return this.lp.isSpeaking;
826
+ }
827
+ get audioLevel() {
828
+ return this.lp.audioLevel;
829
+ }
830
+ get tracks() {
831
+ return mapParticipant(this.lp).tracks;
832
+ }
833
+ // Camera
834
+ get isCameraEnabled() {
835
+ return !!this.lp.getTrackPublication(Track.Source.Camera)?.track && !this.lp.getTrackPublication(Track.Source.Camera)?.isMuted;
836
+ }
837
+ async enableCamera(options) {
838
+ await this.lp.setCameraEnabled(true, {
839
+ deviceId: options?.deviceId,
840
+ resolution: options?.resolution ? { width: options.resolution.width, height: options.resolution.height, frameRate: options.resolution.frameRate } : void 0
841
+ });
842
+ }
843
+ async disableCamera() {
844
+ await this.lp.setCameraEnabled(false);
845
+ }
846
+ // Microphone
847
+ get isMicrophoneEnabled() {
848
+ return !!this.lp.getTrackPublication(Track.Source.Microphone)?.track && !this.lp.getTrackPublication(Track.Source.Microphone)?.isMuted;
849
+ }
850
+ async enableMicrophone(options) {
851
+ await this.lp.setMicrophoneEnabled(true, { deviceId: options?.deviceId });
852
+ }
853
+ async disableMicrophone() {
854
+ await this.lp.setMicrophoneEnabled(false);
855
+ }
856
+ // Screen share
857
+ get isScreenShareEnabled() {
858
+ return !!this.lp.getTrackPublication(Track.Source.ScreenShare)?.track;
859
+ }
860
+ async enableScreenShare(options) {
861
+ await this.lp.setScreenShareEnabled(true, { audio: options?.audio });
862
+ }
863
+ async disableScreenShare() {
864
+ await this.lp.setScreenShareEnabled(false);
865
+ }
866
+ // Metadata
867
+ async setName(name) {
868
+ await this.lp.setName(name);
869
+ }
870
+ async setMetadata(metadata) {
871
+ await this.lp.setMetadata(metadata);
872
+ }
873
+ // Data
874
+ async sendData(data, options) {
875
+ await this.lp.publishData(data, { reliable: options?.reliable ?? true });
876
+ }
877
+ };
878
+ var MediaRoom = class _MediaRoom {
879
+ room;
880
+ listeners = /* @__PURE__ */ new Map();
881
+ _localParticipant;
882
+ constructor() {
883
+ this.room = new Room();
884
+ }
885
+ async connect(url, token, options) {
886
+ const opts = {};
887
+ if (options?.autoSubscribe !== void 0) opts.autoSubscribe = options.autoSubscribe;
888
+ this.room.options.adaptiveStream = options?.adaptiveStream ?? true;
889
+ this.room.options.dynacast = options?.dynacast ?? true;
890
+ this.wireEvents();
891
+ await this.room.connect(url, token, opts);
892
+ this._localParticipant = new MediaLocalParticipant(this.room.localParticipant);
893
+ }
894
+ async disconnect() {
895
+ await this.room.disconnect();
896
+ }
897
+ get state() {
898
+ switch (this.room.state) {
899
+ case ConnectionState.Connected:
900
+ return "connected";
901
+ case ConnectionState.Connecting:
902
+ return "connecting";
903
+ case ConnectionState.Reconnecting:
904
+ return "reconnecting";
905
+ default:
906
+ return "disconnected";
907
+ }
908
+ }
909
+ get localParticipant() {
910
+ return this._localParticipant;
911
+ }
912
+ get participants() {
913
+ const map = /* @__PURE__ */ new Map();
914
+ this.room.remoteParticipants.forEach((p, id) => map.set(id, mapParticipant(p)));
915
+ return map;
916
+ }
917
+ get activeSpeakers() {
918
+ return this.room.activeSpeakers.map(mapParticipant);
919
+ }
920
+ get name() {
921
+ return this.room.name;
922
+ }
923
+ get numParticipants() {
924
+ return this.room.numParticipants;
925
+ }
926
+ get isRecording() {
927
+ return this.room.isRecording;
928
+ }
929
+ // Events
930
+ on(event, callback) {
931
+ if (!this.listeners.has(event)) this.listeners.set(event, /* @__PURE__ */ new Set());
932
+ this.listeners.get(event).add(callback);
933
+ return this;
934
+ }
935
+ off(event, callback) {
936
+ this.listeners.get(event)?.delete(callback);
937
+ return this;
938
+ }
939
+ emit(event, ...args) {
940
+ this.listeners.get(event)?.forEach((cb) => cb(...args));
941
+ }
942
+ // Device switching
943
+ async switchCamera(deviceId) {
944
+ await this.room.switchActiveDevice("videoinput", deviceId);
945
+ }
946
+ async switchMicrophone(deviceId) {
947
+ await this.room.switchActiveDevice("audioinput", deviceId);
948
+ }
949
+ async switchSpeaker(deviceId) {
950
+ await this.room.switchActiveDevice("audiooutput", deviceId);
951
+ }
952
+ // Static device enumeration
953
+ static async getDevices() {
954
+ return navigator.mediaDevices.enumerateDevices();
955
+ }
956
+ static async getCameras() {
957
+ return (await _MediaRoom.getDevices()).filter((d) => d.kind === "videoinput");
958
+ }
959
+ static async getMicrophones() {
960
+ return (await _MediaRoom.getDevices()).filter((d) => d.kind === "audioinput");
961
+ }
962
+ static async getSpeakers() {
963
+ return (await _MediaRoom.getDevices()).filter((d) => d.kind === "audiooutput");
964
+ }
965
+ // ─── Event wiring (LiveKit → MediaRoom) ───
966
+ wireEvents() {
967
+ const r = this.room;
968
+ r.on(RoomEvent.Connected, () => this.emit("connected" /* Connected */));
969
+ r.on(RoomEvent.Reconnecting, () => this.emit("reconnecting" /* Reconnecting */));
970
+ r.on(RoomEvent.Reconnected, () => this.emit("reconnected" /* Reconnected */));
971
+ r.on(RoomEvent.Disconnected, (reason) => this.emit("disconnected" /* Disconnected */, reason));
972
+ r.on(RoomEvent.MediaDevicesChanged, () => this.emit("mediaDevicesChanged" /* MediaDevicesChanged */));
973
+ r.on(RoomEvent.RecordingStatusChanged, (recording) => this.emit("recordingStatusChanged" /* RecordingStatusChanged */, recording));
974
+ r.on(RoomEvent.ParticipantConnected, (p) => this.emit("participantJoined" /* ParticipantJoined */, mapParticipant(p)));
975
+ r.on(RoomEvent.ParticipantDisconnected, (p) => this.emit("participantLeft" /* ParticipantLeft */, mapParticipant(p)));
976
+ r.on(RoomEvent.TrackSubscribed, (track, pub, p) => this.emit("trackSubscribed" /* TrackSubscribed */, track.mediaStreamTrack, mapParticipant(p)));
977
+ r.on(RoomEvent.TrackUnsubscribed, (track, pub, p) => this.emit("trackUnsubscribed" /* TrackUnsubscribed */, track.mediaStreamTrack, mapParticipant(p)));
978
+ r.on(RoomEvent.TrackMuted, (pub, p) => this.emit("trackMuted" /* TrackMuted */, mapParticipant(p)));
979
+ r.on(RoomEvent.TrackUnmuted, (pub, p) => this.emit("trackUnmuted" /* TrackUnmuted */, mapParticipant(p)));
980
+ r.on(RoomEvent.ActiveSpeakersChanged, (speakers) => this.emit("activeSpeakersChanged" /* ActiveSpeakersChanged */, speakers.map(mapParticipant)));
981
+ r.on(RoomEvent.DataReceived, (data, p) => this.emit("dataReceived" /* DataReceived */, data, p ? mapParticipant(p) : void 0));
982
+ }
983
+ };
984
+
453
985
  // src/index.ts
454
986
  var cachedPlatform = null;
455
987
  function getPlatform(options) {
@@ -484,8 +1016,15 @@ function clearPlatformCache() {
484
1016
  }
485
1017
  }
486
1018
  export {
1019
+ MediaLocalParticipant,
1020
+ MediaRoom,
1021
+ MediaRoomEvent,
1022
+ RealtimeClient,
1023
+ RemoteMediaService,
487
1024
  RenClient,
1025
+ attachTrack,
488
1026
  clearPlatformCache,
1027
+ detachTrack,
489
1028
  getOrCreateClientId,
490
1029
  getPlatform,
491
1030
  renFetch,