UNPKG

@simplito/privmx-webendpoint

Version:

PrivMX Web Endpoint library

437 lines (436 loc) 19.1 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.StreamApi = void 0; const Utils_1 = require("../webStreams/Utils"); const BaseApi_1 = require("./BaseApi"); /** * `StreamApi` is a class representing Endpoint's API for Stream Rooms. */ class StreamApi extends BaseApi_1.BaseApi { native; client; constructor(native, ptr, client) { super(ptr); this.native = native; this.client = client; } // local data streams = new Map(); streamTracks = new Map(); /** * Creates a new Stream Room in given Context. * * @param {string} contextId ID of the Context to create the Stream Room in * @param {UserWithPubKey[]} users array of UserWithPubKey structs which indicates who will have access to the created Stream Room * @param {UserWithPubKey[]} managers array of UserWithPubKey structs which indicates who will have access (and management rights) to the created Stream Room * @param {Uint8Array} publicMeta public (unencrypted) metadata * @param {Uint8Array} privateMeta private (encrypted) metadata * @param {ContainerPolicy} policies Stream Room's policies (pass `undefined` to use defaults) * @returns {string} ID of the created Stream Room */ async createStreamRoom(contextId, users, managers, publicMeta, privateMeta, policies) { const res = await this.native.createStreamRoom(this.servicePtr, [ contextId, users, managers, publicMeta, privateMeta, policies, ]); return res; } /** * Updates an existing Stream Room. * * @param {string} streamRoomId ID of the Stream Room to update * @param {UserWithPubKey[]} users array of UserWithPubKey structs which indicates who will have access to the Stream Room * @param {UserWithPubKey[]} managers array of UserWithPubKey structs which indicates who will have access (and management rights) to the Stream Room * @param {Uint8Array} publicMeta public (unencrypted) metadata * @param {Uint8Array} privateMeta private (encrypted) metadata * @param {number} version current version of the updated Stream Room * @param {boolean} force force update (without checking version) * @param {boolean} forceGenerateNewKey force to regenerate a key for the Stream Room * @param {ContainerPolicy} policies Stream Room's policies (pass `undefined` to keep current/defaults) */ async updateStreamRoom(streamRoomId, users, managers, publicMeta, privateMeta, version, force, forceGenerateNewKey, policies) { return this.native.updateStreamRoom(this.servicePtr, [ streamRoomId, users, managers, publicMeta, privateMeta, version, force, forceGenerateNewKey, policies, ]); } /** * Gets a list of Stream Rooms in given Context. * * @param {string} contextId ID of the Context to get the Stream Rooms from * @param {PagingQuery} query struct with list query parameters * @returns {PagingList<StreamRoom>} list of Stream Rooms */ async listStreamRooms(contextId, query) { return this.native.listStreamRooms(this.servicePtr, [contextId, query]); } /** * Joins a Stream Room. * * This is required before calling `createStream`/`publishStream` and before subscribing to remote streams * in the room. * * @param {string} streamRoomId ID of the Stream Room to join */ async joinStreamRoom(streamRoomId) { return this.native.joinStreamRoom(this.servicePtr, [streamRoomId]); } /** * Leaves a Stream Room. * * @param {string} streamRoomId ID of the Stream Room to leave */ async leaveStreamRoom(streamRoomId) { return this.native.leaveStreamRoom(this.servicePtr, [streamRoomId]); } /** * Enables server-side recording for the Stream Room. * * @param {string} streamRoomId ID of the Stream Room */ async enableStreamRoomRecording(streamRoomId) { return this.native.enableStreamRoomRecording(this.servicePtr, [streamRoomId]); } /** * Gets encryption keys used for Stream Room recordings. * * @param {string} streamRoomId ID of the Stream Room * @returns {EndpointTypes.RecordingEncKey[]} list of recording encryption keys */ async getStreamRoomRecordingKeys(streamRoomId) { return this.native.getStreamRoomRecordingKeys(this.servicePtr, [streamRoomId]); } /** * Gets a single Stream Room by given Stream Room ID. * * @param {string} streamRoomId ID of the Stream Room to get * @returns {StreamRoom} information about the Stream Room */ async getStreamRoom(streamRoomId) { return this.native.getStreamRoom(this.servicePtr, [streamRoomId]); } /** * Deletes a Stream Room by given Stream Room ID. * * @param {string} streamRoomId ID of the Stream Room to delete */ async deleteStreamRoom(streamRoomId) { return this.native.deleteStreamRoom(this.servicePtr, [streamRoomId]); } /** * Creates a local Stream handle for publishing media in given Stream Room. * * Call `addStreamTrack`/`removeStreamTrack` to stage tracks and `publishStream`/`updateStream` to send * changes to the server. * * @param {string} streamRoomId ID of the Stream Room to create the stream in * @returns {StreamHandle} handle to a local Stream instance */ async createStream(streamRoomId) { const meta = {}; // tutaj uzupelniajac opcjonalne pola obiektu meta mozemy ustawiac w Janusie dodatkowe rzeczy const handle = await this.native.createStream(this.servicePtr, [streamRoomId]); this.streams.set(handle, { handle, streamRoomId, createStreamMeta: meta, remote: false }); return handle; } /** * Gets a list of currently published streams in given Stream Room. * * @param {string} streamRoomId ID of the Stream Room to list streams from * @returns {StreamInfo[]} list of StreamInfo structs describing currently published streams */ async listStreams(streamRoomId) { const remoteStreams = await this.native.listStreams(this.servicePtr, [streamRoomId]); return remoteStreams; } /** * Adds a local media track definition to a Stream handle. * * The track is staged locally and becomes visible to others after `publishStream`/`updateStream`. * * @param {StreamHandle} streamHandle handle returned by `createStream` * @param {Types.StreamTrackInit} meta track/data channel metadata (track: `MediaStreamTrack`, dataChannel: `DataChannelMeta`) * @returns {string} StreamTrackId assigned locally for this track * @throws {Error} when the given `streamHandle` does not exist or the same browser track is already staged */ async addStreamTrack(streamHandle, meta) { if (!this.streams.has(streamHandle)) { throw new Error("[addStreamTrack]: there is no Stream with given Id: " + streamHandle); } let alreadyAddedId = ""; const tracksByHandle = Array.from(this.streamTracks.values()).filter((x) => x.streamHandle === streamHandle); for (const streamTrack of tracksByHandle) { if (streamTrack.track && streamTrack.track.id === meta.track?.id) { if (streamTrack.markedToRemove === true) { streamTrack.markedToRemove = undefined; alreadyAddedId = streamTrack.id; break; } else { throw new Error("[addStreamTrack] StreamTrack with given browser's track already added."); } } } if (alreadyAddedId.length > 0) { return alreadyAddedId; } const stream = this.streams.get(streamHandle); if (!stream) { throw new Error("Cannot find stream by id"); } const streamTrackId = Utils_1.Utils.getRandomString(16); const streamTrack = { id: streamTrackId, streamHandle: streamHandle, track: meta.track, dataChannelMeta: { created: meta.createDataChannel }, published: false, }; this.streamTracks.set(streamTrackId, streamTrack); return streamTrackId; } /** * Removes a previously added media track from a Stream handle. * * For already published streams the removal is applied on `updateStream`. * * @param {StreamHandle} streamHandle handle returned by `createStream` * @param {Types.StreamTrackInit} meta media track metadata previously passed to `addStreamTrack` * @throws {Error} when the given `streamHandle` does not exist */ async removeStreamTrack(streamHandle, meta) { if (!this.streams.has(streamHandle)) { throw new Error("[removeStreamTrack]: there is no Stream with given Id: " + streamHandle); } for (const [key, streamTrack] of this.streamTracks.entries()) { if (streamTrack.track && streamTrack.track?.id === meta.track?.id && streamTrack.streamHandle === streamHandle) { streamTrack.markedToRemove = true; } } } /** * Publishes the Stream (with currently staged tracks) to the server. * * @param {StreamHandle} streamHandle handle returned by `createStream` * @param {(state: RTCPeerConnectionState) => void} onStreamState optional callback invoked on RTCPeerConnection state changes * @returns {StreamPublishResult} result of the publish operation * @throws {Error} when the given `streamHandle` does not exist */ async publishStream(streamHandle, onStreamState) { const mediaTracks = []; const dataTracks = []; for (const value of this.streamTracks.values()) { let toPublish = false; if (value.streamHandle === streamHandle && value.track && !value.markedToRemove && value.published === false) { mediaTracks.push(value.track); // value.published = true; toPublish = true; } if (value.streamHandle === streamHandle && value.dataChannelMeta.created === true && !value.markedToRemove && value.published === false) { dataTracks.push(value); toPublish = true; } value.published = toPublish; } const _stream = this.streams.get(streamHandle); if (!_stream) { throw new Error("No stream defined to publish"); } _stream.localMediaStream = mediaTracks.length > 0 ? new MediaStream(mediaTracks) : undefined; const turnCredentials = await this.native.getTurnCredentials(this.servicePtr, []); await this.client.setTurnCredentials(turnCredentials); await this.client.createPeerConnectionWithLocalStream(streamHandle, _stream.streamRoomId, _stream.localMediaStream, dataTracks); if (onStreamState && typeof onStreamState === "function") { this.client .getStreamStateChangeDispatcher() .addOnStateChangeListener({ streamHandle: streamHandle }, (event) => onStreamState(event.state)); } const res = await this.native.publishStream(this.servicePtr, [streamHandle]); return res; } /** * Updates a published Stream after adding/removing tracks. * * @param {StreamHandle} streamHandle handle returned by `createStream` * @returns {StreamPublishResult} result of the update operation * @throws {Error} when the given `streamHandle` does not exist */ async updateStream(streamHandle) { // configure client const tracksToAdd = []; const tracksToRemove = []; for (const value of this.streamTracks.values()) { if (value.streamHandle === streamHandle && value.track) { if (!value.published && !value.markedToRemove) { tracksToAdd.push(value.track); } if (value.markedToRemove) { tracksToRemove.push(value.track); } } } const _stream = this.streams.get(streamHandle); if (!_stream) { throw new Error("No stream defined to publish"); } const turnCredentials = await this.native.getTurnCredentials(this.servicePtr, []); await this.client.setTurnCredentials(turnCredentials); await this.client.updatePeerConnectionWithLocalStream(_stream.streamRoomId, _stream.localMediaStream, tracksToAdd, tracksToRemove); const res = await this.native.updateStream(this.servicePtr, [streamHandle]); return res; } filterMapByValue(map, predicate) { const result = new Map(); for (const [key, value] of map) { if (predicate(value, key)) { result.set(key, value); } } return result; } /** * Stops publishing the Stream. * * @param {StreamHandle} streamHandle handle returned by `createStream` * @throws {Error} when the given `streamHandle` does not exist */ async unpublishStream(streamHandle) { if (!this.streams.has(streamHandle)) { throw new Error("No local stream with given id to unpublish"); } const _stream = this.streams.get(streamHandle); const filteredTracks = this.filterMapByValue(this.streamTracks, (x) => x.streamHandle !== streamHandle); this.streamTracks = filteredTracks; await this.native.unpublishStream(this.servicePtr, [streamHandle]); this.client.removeSenderPeerConnectionOnUnpublish(_stream.streamRoomId, _stream.localMediaStream); this.streams.delete(streamHandle); this.client.getStreamStateChangeDispatcher().removeOnStateChangeListener({ streamHandle }); } /** * Subscribes to selected remote streams (and optionally specific tracks) in the Stream Room. * * @param {string} streamRoomId ID of the Stream Room * @param {EndpointTypes.StreamSubscription[]} subscriptions list of remote streams/tracks to subscribe to */ async subscribeToRemoteStreams(streamRoomId, subscriptions) { // native part const peerCredentials = await this.native.getTurnCredentials(this.servicePtr, []); await this.client.setTurnCredentials(peerCredentials); // server / core part await this.native.subscribeToRemoteStreams(this.servicePtr, [streamRoomId, subscriptions]); this.client.getConnectionManager().initialize(streamRoomId, "subscriber"); } /** * Modifies current remote streams subscriptions. * * @param {string} streamRoomId ID of the Stream Room * @param {EndpointTypes.StreamSubscription[]} subscriptionsToAdd list of subscriptions to add * @param {StreamSubscription[]} subscriptionsToRemove list of subscriptions to remove */ async modifyRemoteStreamsSubscriptions(streamRoomId, subscriptionsToAdd, subscriptionsToRemove) { await this.native.modifyRemoteStreamsSubscriptions(this.servicePtr, [ streamRoomId, subscriptionsToAdd, subscriptionsToRemove, ]); } /** * Unsubscribes from selected remote streams (and optionally specific tracks) in the Stream Room. * * @param {string} streamRoomId ID of the Stream Room * @param {StreamSubscription[]} subscriptions list of subscriptions to remove */ async unsubscribeFromRemoteStreams(streamRoomId, subscriptions) { await this.native.unsubscribeFromRemoteStreams(this.servicePtr, [ streamRoomId, subscriptions, ]); } /** * Registers a listener for remote tracks in the Stream Room. * * @param {RemoteStreamListener} listener listener configuration * @param {string} listener.streamRoomId ID of the Stream Room * @param {number} [listener.streamId] optional remote Stream ID to filter events (omit for all streams) * @param {(event: RTCTrackEvent) => void} listener.onRemoteStreamTrack callback invoked for incoming remote tracks */ addRemoteStreamListener(listener) { this.client.addRemoteStreamListener(listener); } /** * Subscribe for the Stream Room events on the given subscription query. * * @param {string[]} subscriptionQueries list of queries * @return list of subscriptionIds in maching order to subscriptionQueries */ async subscribeFor(subscriptionQueries) { return this.native.subscribeFor(this.servicePtr, [subscriptionQueries]); } /** * Unsubscribe from events for the given subscriptionId. * @param {string[]} subscriptionIds list of subscriptionId */ async unsubscribeFrom(subscriptionIds) { return this.native.unsubscribeFrom(this.servicePtr, [subscriptionIds]); } /** * Generate subscription Query for the Stream Room events. * @param {EventType} eventType type of event which you listen for * @param {EventSelectorType} selectorType scope on which you listen for events * @param {string} selectorId ID of the selector * @returns {string} subscription ID */ async buildSubscriptionQuery(eventType, selectorType, selectorId) { return this.native.buildSubscriptionQuery(this.servicePtr, [ eventType, selectorType, selectorId, ]); } /** * Registers a callback for audio level statistics produced by the WebRTC client. * * @param {(stats: AudioLevelsStats) => void} onStats callback invoked with current audio levels stats */ async addAudioLevelStatsListener(onStats) { if (onStats && typeof onStats === "function") { this.client.setAudioLevelCallback(onStats); } } /** * Sends binary data over a WebRTC DataChannel associated with a published Stream data track. * * @param {Types.StreamTrackId} streamTrackId StreamTrackId of the data track created via `addStreamTrack` * @param {Uint8Array} data bytes to send to remote participants * @throws {Error} when there is no DataTrack (or DataChannel) for the given `streamTrackId` */ async sendData(streamTrackId, data) { const dataChannel = this.streamTracks.get(streamTrackId)?.dataChannelMeta.dataChannel; if (!dataChannel) { throw new Error(`There is no DataTrack with given streamTrackId: ${streamTrackId}`); } const frame = await this.client.encryptDataChannelData(data); dataChannel.send(frame); } } exports.StreamApi = StreamApi;