tas-uell-sdk 0.0.1

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.
@@ -0,0 +1,705 @@
1
+ import * as i0 from '@angular/core';
2
+ import { InjectionToken, Injectable, Optional, Inject, Component, Input, ViewChild, NgModule } from '@angular/core';
3
+ import * as i3 from '@angular/common';
4
+ import { CommonModule } from '@angular/common';
5
+ import * as i1 from '@ng-bootstrap/ng-bootstrap';
6
+ import { NgbModalModule } from '@ng-bootstrap/ng-bootstrap';
7
+ import { BehaviorSubject, Subscription } from 'rxjs';
8
+ import { map, catchError, switchMap } from 'rxjs/operators';
9
+ import * as OT from '@opentok/client';
10
+ import interact from 'interactjs';
11
+
12
+ /**
13
+ * Injection token for TAS configuration
14
+ */
15
+ const TAS_CONFIG = new InjectionToken("TAS_CONFIG");
16
+ /**
17
+ * Injection token for HTTP client
18
+ */
19
+ const TAS_HTTP_CLIENT = new InjectionToken("TAS_HTTP_CLIENT");
20
+ /**
21
+ * Injection token for user data provider
22
+ */
23
+ const TAS_USER_DATA_PROVIDER = new InjectionToken("TAS_USER_DATA_PROVIDER");
24
+
25
+ var CallState;
26
+ (function (CallState) {
27
+ CallState["IDLE"] = "IDLE";
28
+ CallState["CONNECTING"] = "CONNECTING";
29
+ CallState["CONNECTED"] = "CONNECTED";
30
+ CallState["DISCONNECTED"] = "DISCONNECTED";
31
+ CallState["ERROR"] = "ERROR";
32
+ })(CallState || (CallState = {}));
33
+ var ViewMode;
34
+ (function (ViewMode) {
35
+ ViewMode["FULLSCREEN"] = "FULLSCREEN";
36
+ ViewMode["PIP"] = "PIP";
37
+ })(ViewMode || (ViewMode = {}));
38
+ class TasService {
39
+ constructor(config, httpClient) {
40
+ this.config = config;
41
+ this.httpClient = httpClient;
42
+ this.session = null;
43
+ this.publisher = null;
44
+ this.subscribers = [];
45
+ this.callStateSubject = new BehaviorSubject(CallState.IDLE);
46
+ this.callState$ = this.callStateSubject.asObservable();
47
+ this.viewModeSubject = new BehaviorSubject(ViewMode.FULLSCREEN);
48
+ this.viewMode$ = this.viewModeSubject.asObservable();
49
+ this.isMutedSubject = new BehaviorSubject(false);
50
+ this.isMuted$ = this.isMutedSubject.asObservable();
51
+ // Session info for PiP mode restoration
52
+ this.currentSessionId = null;
53
+ this.currentToken = null;
54
+ if (!this.config || !this.httpClient) {
55
+ console.warn("TasService: Configuration not provided. Make sure to use TasModule.forRoot()");
56
+ }
57
+ }
58
+ // Getters
59
+ get currentSession() {
60
+ return this.session;
61
+ }
62
+ get currentPublisher() {
63
+ return this.publisher;
64
+ }
65
+ get currentSubscribers() {
66
+ return this.subscribers;
67
+ }
68
+ get sessionId() {
69
+ return this.currentSessionId;
70
+ }
71
+ get token() {
72
+ return this.currentToken;
73
+ }
74
+ get isMuted() {
75
+ return this.isMutedSubject.getValue();
76
+ }
77
+ // View Mode Methods
78
+ setViewMode(mode) {
79
+ this.viewModeSubject.next(mode);
80
+ }
81
+ enterPipMode() {
82
+ this.viewModeSubject.next(ViewMode.PIP);
83
+ }
84
+ exitPipMode() {
85
+ this.viewModeSubject.next(ViewMode.FULLSCREEN);
86
+ }
87
+ isPipMode() {
88
+ return this.viewModeSubject.getValue() === ViewMode.PIP;
89
+ }
90
+ // Audio Control
91
+ toggleMute() {
92
+ if (this.publisher) {
93
+ const newMuteState = !this.isMutedSubject.getValue();
94
+ this.publisher.publishAudio(!newMuteState);
95
+ this.isMutedSubject.next(newMuteState);
96
+ }
97
+ }
98
+ setMute(muted) {
99
+ if (this.publisher) {
100
+ this.publisher.publishAudio(!muted);
101
+ this.isMutedSubject.next(muted);
102
+ }
103
+ }
104
+ // Session Management
105
+ disconnectSession() {
106
+ if (this.session) {
107
+ this.session.disconnect();
108
+ this.session = null;
109
+ }
110
+ this.publisher = null;
111
+ this.subscribers = [];
112
+ this.currentSessionId = null;
113
+ this.currentToken = null;
114
+ this.isMutedSubject.next(false);
115
+ this.viewModeSubject.next(ViewMode.FULLSCREEN);
116
+ this.callStateSubject.next(CallState.DISCONNECTED);
117
+ }
118
+ isCallActive() {
119
+ return this.callStateSubject.getValue() === CallState.CONNECTED;
120
+ }
121
+ // API Methods
122
+ createRoom(payload) {
123
+ if (!this.config || !this.httpClient) {
124
+ throw new Error("TasService not configured. Use TasModule.forRoot()");
125
+ }
126
+ const url = `${this.config.apiBaseUrl}/v2/room`;
127
+ return this.httpClient.post(url, payload).pipe(map((response) => response), catchError((error) => {
128
+ console.error("TAS Service: createRoom failed", error);
129
+ throw error;
130
+ }));
131
+ }
132
+ generateToken(payload) {
133
+ if (!this.config || !this.httpClient) {
134
+ throw new Error("TasService not configured. Use TasModule.forRoot()");
135
+ }
136
+ const url = `${this.config.apiBaseUrl}/v2/room/token`;
137
+ return this.httpClient.post(url, payload).pipe(map((response) => response), catchError((error) => {
138
+ console.error("TAS Service: generateToken failed", error);
139
+ throw error;
140
+ }));
141
+ }
142
+ /**
143
+ * Connects to a TokBox video session
144
+ */
145
+ connectSession(sessionId, token, publisherElement, subscriberElement) {
146
+ this.callStateSubject.next(CallState.CONNECTING);
147
+ this.currentSessionId = sessionId;
148
+ this.currentToken = token;
149
+ return new Promise((resolve, reject) => {
150
+ if (!this.config) {
151
+ this.callStateSubject.next(CallState.ERROR);
152
+ reject(new Error("TasService not configured. Use TasModule.forRoot()"));
153
+ return;
154
+ }
155
+ if (!OT.checkSystemRequirements()) {
156
+ this.callStateSubject.next(CallState.ERROR);
157
+ reject(new Error("Browser not compatible with TokBox"));
158
+ return;
159
+ }
160
+ this.session = OT.initSession(this.config.tokBoxApiKey, sessionId);
161
+ // Handle new streams (remote participants joining)
162
+ this.session.on("streamCreated", (event) => {
163
+ var _a;
164
+ const subscriber = (_a = this.session) === null || _a === void 0 ? void 0 : _a.subscribe(event.stream, subscriberElement, {
165
+ insertMode: "append",
166
+ width: "100%",
167
+ height: "100%",
168
+ }, (error) => {
169
+ if (error) {
170
+ console.error("Error subscribing to stream:", error);
171
+ }
172
+ });
173
+ if (subscriber) {
174
+ this.subscribers.push(subscriber);
175
+ }
176
+ });
177
+ // Handle streams ending (remote participants leaving)
178
+ this.session.on("streamDestroyed", (event) => {
179
+ this.subscribers = this.subscribers.filter((sub) => { var _a; return ((_a = sub.stream) === null || _a === void 0 ? void 0 : _a.streamId) !== event.stream.streamId; });
180
+ });
181
+ // Handle session disconnection
182
+ this.session.on("sessionDisconnected", () => {
183
+ this.callStateSubject.next(CallState.DISCONNECTED);
184
+ });
185
+ // Connect to session
186
+ this.session.connect(token, (error) => {
187
+ if (error) {
188
+ console.error("Error connecting to session:", error);
189
+ this.callStateSubject.next(CallState.ERROR);
190
+ reject(error);
191
+ return;
192
+ }
193
+ // Initialize publisher (local video)
194
+ this.publisher = OT.initPublisher(publisherElement, {
195
+ insertMode: "append",
196
+ width: "100%",
197
+ height: "100%",
198
+ }, (err) => {
199
+ var _a;
200
+ if (err) {
201
+ console.error("Error initializing publisher:", err);
202
+ this.callStateSubject.next(CallState.ERROR);
203
+ reject(err);
204
+ return;
205
+ }
206
+ // Publish to session
207
+ (_a = this.session) === null || _a === void 0 ? void 0 : _a.publish(this.publisher, (pubErr) => {
208
+ if (pubErr) {
209
+ console.error("Error publishing stream:", pubErr);
210
+ this.callStateSubject.next(CallState.ERROR);
211
+ reject(pubErr);
212
+ }
213
+ else {
214
+ this.callStateSubject.next(CallState.CONNECTED);
215
+ resolve();
216
+ }
217
+ });
218
+ });
219
+ });
220
+ });
221
+ }
222
+ // Video Element Movement Methods
223
+ movePublisherTo(newContainerId) {
224
+ if (!this.publisher)
225
+ return;
226
+ const publisherElement = this.publisher.element;
227
+ const newContainer = document.getElementById(newContainerId);
228
+ if (publisherElement && newContainer) {
229
+ newContainer.appendChild(publisherElement);
230
+ }
231
+ }
232
+ moveSubscribersTo(newContainerId) {
233
+ const newContainer = document.getElementById(newContainerId);
234
+ if (!newContainer)
235
+ return;
236
+ this.subscribers.forEach((subscriber) => {
237
+ const subscriberElement = subscriber.element;
238
+ if (subscriberElement) {
239
+ newContainer.appendChild(subscriberElement);
240
+ }
241
+ });
242
+ }
243
+ /**
244
+ * Moves the main video (subscriber if available, otherwise publisher) to a container.
245
+ * Used for PiP mode to show only one video.
246
+ */
247
+ moveMainVideoTo(newContainerId) {
248
+ var _a;
249
+ const newContainer = document.getElementById(newContainerId);
250
+ if (!newContainer)
251
+ return;
252
+ // Prefer remote video (subscriber) as main
253
+ if (this.subscribers.length > 0 && this.subscribers[0].element) {
254
+ newContainer.appendChild(this.subscribers[0].element);
255
+ return;
256
+ }
257
+ // Fall back to local video (publisher)
258
+ if ((_a = this.publisher) === null || _a === void 0 ? void 0 : _a.element) {
259
+ newContainer.appendChild(this.publisher.element);
260
+ }
261
+ }
262
+ /**
263
+ * Moves videos back to fullscreen containers
264
+ */
265
+ moveVideosToFullscreen() {
266
+ this.moveSubscribersTo("subscriber-container");
267
+ this.movePublisherTo("publisher-container");
268
+ }
269
+ }
270
+ TasService.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "13.4.0", ngImport: i0, type: TasService, deps: [{ token: TAS_CONFIG, optional: true }, { token: TAS_HTTP_CLIENT, optional: true }], target: i0.ɵɵFactoryTarget.Injectable });
271
+ TasService.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "13.4.0", ngImport: i0, type: TasService, providedIn: "root" });
272
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "13.4.0", ngImport: i0, type: TasService, decorators: [{
273
+ type: Injectable,
274
+ args: [{ providedIn: "root" }]
275
+ }], ctorParameters: function () {
276
+ return [{ type: undefined, decorators: [{
277
+ type: Optional
278
+ }, {
279
+ type: Inject,
280
+ args: [TAS_CONFIG]
281
+ }] }, { type: undefined, decorators: [{
282
+ type: Optional
283
+ }, {
284
+ type: Inject,
285
+ args: [TAS_HTTP_CLIENT]
286
+ }] }];
287
+ } });
288
+
289
+ class TasVideocallComponent {
290
+ constructor(activeModal, tasService) {
291
+ this.activeModal = activeModal;
292
+ this.tasService = tasService;
293
+ this.isReturningFromPip = false;
294
+ this.isPublisherSmall = true;
295
+ this.callState = CallState.IDLE;
296
+ this.isMuted = false;
297
+ this.subscriptions = new Subscription();
298
+ }
299
+ ngOnInit() {
300
+ this.setupSubscriptions();
301
+ this.initializeCall();
302
+ }
303
+ ngAfterViewInit() {
304
+ this.initInteract();
305
+ }
306
+ ngOnDestroy() {
307
+ this.subscriptions.unsubscribe();
308
+ if (!this.tasService.isPipMode()) {
309
+ this.tasService.disconnectSession();
310
+ }
311
+ interact(".publisher-view").unset();
312
+ }
313
+ // Public Methods
314
+ hangUp() {
315
+ this.tasService.disconnectSession();
316
+ }
317
+ toggleMute() {
318
+ this.tasService.toggleMute();
319
+ }
320
+ minimize() {
321
+ this.tasService.moveMainVideoTo("pip-main-video");
322
+ setTimeout(() => this.tasService.enterPipMode(), 50);
323
+ }
324
+ toggleSwap() {
325
+ this.isPublisherSmall = !this.isPublisherSmall;
326
+ this.resetVideoPositions();
327
+ setTimeout(() => this.initInteract());
328
+ }
329
+ onDoubleClick() {
330
+ this.toggleSwap();
331
+ }
332
+ // Private Methods
333
+ setupSubscriptions() {
334
+ this.subscriptions.add(this.tasService.callState$.subscribe((state) => {
335
+ this.callState = state;
336
+ if (state === CallState.DISCONNECTED) {
337
+ this.activeModal.close("hangup");
338
+ }
339
+ }));
340
+ this.subscriptions.add(this.tasService.viewMode$.subscribe((mode) => {
341
+ if (mode === ViewMode.PIP) {
342
+ this.activeModal.close("pip");
343
+ }
344
+ }));
345
+ this.subscriptions.add(this.tasService.isMuted$.subscribe((muted) => {
346
+ this.isMuted = muted;
347
+ }));
348
+ }
349
+ initializeCall() {
350
+ if (this.isReturningFromPip) {
351
+ setTimeout(() => this.tasService.moveVideosToFullscreen(), 100);
352
+ }
353
+ else if (this.sessionId && this.token) {
354
+ this.tasService
355
+ .connectSession(this.sessionId, this.token, "publisher-container", "subscriber-container")
356
+ .catch((err) => {
357
+ console.error("Error connecting to video call:", err);
358
+ });
359
+ }
360
+ }
361
+ resetVideoPositions() {
362
+ var _a, _b;
363
+ const publisherEl = (_a = this.publisherContainer) === null || _a === void 0 ? void 0 : _a.nativeElement;
364
+ const subscriberEl = (_b = this.subscriberContainer) === null || _b === void 0 ? void 0 : _b.nativeElement;
365
+ if (publisherEl) {
366
+ publisherEl.removeAttribute("style");
367
+ publisherEl.removeAttribute("data-x");
368
+ publisherEl.removeAttribute("data-y");
369
+ }
370
+ if (subscriberEl) {
371
+ subscriberEl.removeAttribute("style");
372
+ subscriberEl.removeAttribute("data-x");
373
+ subscriberEl.removeAttribute("data-y");
374
+ }
375
+ }
376
+ initInteract() {
377
+ interact(".publisher-view").unset();
378
+ interact(".publisher-view")
379
+ .draggable({
380
+ inertia: true,
381
+ modifiers: [
382
+ interact.modifiers.restrictRect({
383
+ restriction: "parent",
384
+ endOnly: true,
385
+ }),
386
+ ],
387
+ autoScroll: true,
388
+ listeners: {
389
+ move: (event) => {
390
+ const target = event.target;
391
+ const x = (parseFloat(target.getAttribute("data-x")) || 0) + event.dx;
392
+ const y = (parseFloat(target.getAttribute("data-y")) || 0) + event.dy;
393
+ target.style.transform = `translate(${x}px, ${y}px)`;
394
+ target.setAttribute("data-x", String(x));
395
+ target.setAttribute("data-y", String(y));
396
+ },
397
+ },
398
+ })
399
+ .resizable({
400
+ edges: { left: false, right: true, bottom: true, top: false },
401
+ listeners: {
402
+ move: (event) => {
403
+ const target = event.target;
404
+ let x = parseFloat(target.getAttribute("data-x")) || 0;
405
+ let y = parseFloat(target.getAttribute("data-y")) || 0;
406
+ target.style.width = `${event.rect.width}px`;
407
+ target.style.height = `${event.rect.height}px`;
408
+ x += event.deltaRect.left;
409
+ y += event.deltaRect.top;
410
+ target.style.transform = `translate(${x}px, ${y}px)`;
411
+ target.setAttribute("data-x", String(x));
412
+ target.setAttribute("data-y", String(y));
413
+ },
414
+ },
415
+ modifiers: [
416
+ interact.modifiers.restrictEdges({ outer: "parent" }),
417
+ interact.modifiers.restrictSize({ min: { width: 150, height: 100 } }),
418
+ interact.modifiers.aspectRatio({ ratio: "preserve" }),
419
+ ],
420
+ inertia: true,
421
+ });
422
+ }
423
+ }
424
+ TasVideocallComponent.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "13.4.0", ngImport: i0, type: TasVideocallComponent, deps: [{ token: i1.NgbActiveModal }, { token: TasService }], target: i0.ɵɵFactoryTarget.Component });
425
+ TasVideocallComponent.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "12.0.0", version: "13.4.0", type: TasVideocallComponent, selector: "tas-videocall", inputs: { sessionId: "sessionId", token: "token", isReturningFromPip: "isReturningFromPip" }, viewQueries: [{ propertyName: "publisherContainer", first: true, predicate: ["publisherContainer"], descendants: true }, { propertyName: "subscriberContainer", first: true, predicate: ["subscriberContainer"], descendants: true }], ngImport: i0, template: "<div class=\"tas-videocall-container\">\n <div\n id=\"subscriber-container\"\n [class.subscriber-view]=\"isPublisherSmall\"\n [class.publisher-view]=\"!isPublisherSmall\"\n #subscriberContainer\n (dblclick)=\"onDoubleClick()\"\n ></div>\n\n <div\n id=\"publisher-container\"\n [class.publisher-view]=\"isPublisherSmall\"\n [class.subscriber-view]=\"!isPublisherSmall\"\n #publisherContainer\n (dblclick)=\"onDoubleClick()\"\n ></div>\n\n <div class=\"controls-container\">\n <button class=\"btn swap-btn\" (click)=\"toggleSwap()\" title=\"Cambiar vista\">\n <i class=\"fa fa-refresh\"></i>\n </button>\n <button\n class=\"btn pip-btn\"\n (click)=\"minimize()\"\n title=\"Minimizar (Picture in Picture)\"\n >\n <i class=\"fa fa-compress\"></i>\n </button>\n <button\n class=\"btn mute-btn\"\n [class.muted]=\"isMuted\"\n (click)=\"toggleMute()\"\n [title]=\"isMuted ? 'Activar micr\u00F3fono' : 'Silenciar micr\u00F3fono'\"\n >\n <i\n class=\"fa\"\n [class.fa-microphone]=\"!isMuted\"\n [class.fa-microphone-slash]=\"isMuted\"\n ></i>\n </button>\n <button class=\"btn hangup-btn\" (click)=\"hangUp()\" title=\"Colgar\">\n <i class=\"fa fa-phone\" style=\"transform: rotate(135deg)\"></i>\n </button>\n </div>\n</div>\n", styles: [".tas-videocall-container{position:relative;width:100vw;height:100vh;background-color:#000;overflow:hidden}.tas-videocall-container ::ng-deep .OT_edge-bar-item,.tas-videocall-container ::ng-deep .OT_mute,.tas-videocall-container ::ng-deep .OT_audio-level-meter,.tas-videocall-container ::ng-deep .OT_bar,.tas-videocall-container ::ng-deep .OT_name{display:none!important}.tas-videocall-container .subscriber-view{width:100%;height:100%;z-index:1}.tas-videocall-container .publisher-view{position:absolute;top:20px;right:20px;width:200px;height:150px;z-index:2;border:2px solid #fff;border-radius:8px;background-color:#333}.tas-videocall-container .controls-container{display:flex;flex-direction:row;gap:20px;position:absolute;bottom:30px;left:50%;transform:translate(-50%);z-index:3;background-color:#00000080;padding:15px 25px;border-radius:50px;backdrop-filter:blur(5px)}.tas-videocall-container .controls-container .hangup-btn,.tas-videocall-container .controls-container .swap-btn,.tas-videocall-container .controls-container .pip-btn,.tas-videocall-container .controls-container .mute-btn{width:60px;height:60px;border-radius:50%;display:flex;align-items:center;justify-content:center;font-size:24px;border:none;box-shadow:0 4px 6px #0000004d;transition:all .2s ease}.tas-videocall-container .controls-container .hangup-btn i,.tas-videocall-container .controls-container .swap-btn i,.tas-videocall-container .controls-container .pip-btn i,.tas-videocall-container .controls-container .mute-btn i{color:#fff}.tas-videocall-container .controls-container .hangup-btn{background:#dc3545}.tas-videocall-container .controls-container .hangup-btn:hover{background:#c82333;transform:scale(1.05)}.tas-videocall-container .controls-container .swap-btn{background:rgba(255,255,255,.2)}.tas-videocall-container .controls-container .swap-btn:hover{background:rgba(255,255,255,.35);transform:scale(1.05)}.tas-videocall-container .controls-container .pip-btn{background:rgba(255,255,255,.2)}.tas-videocall-container .controls-container .pip-btn:hover{background:rgba(255,255,255,.35);transform:scale(1.05)}.tas-videocall-container .controls-container .mute-btn{background:rgba(255,255,255,.2)}.tas-videocall-container .controls-container .mute-btn:hover{background:rgba(255,255,255,.35);transform:scale(1.05)}.tas-videocall-container .controls-container .mute-btn.muted{background:#f39c12}.tas-videocall-container .controls-container .mute-btn.muted:hover{background:#e67e22}\n"] });
426
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "13.4.0", ngImport: i0, type: TasVideocallComponent, decorators: [{
427
+ type: Component,
428
+ args: [{ selector: "tas-videocall", template: "<div class=\"tas-videocall-container\">\n <div\n id=\"subscriber-container\"\n [class.subscriber-view]=\"isPublisherSmall\"\n [class.publisher-view]=\"!isPublisherSmall\"\n #subscriberContainer\n (dblclick)=\"onDoubleClick()\"\n ></div>\n\n <div\n id=\"publisher-container\"\n [class.publisher-view]=\"isPublisherSmall\"\n [class.subscriber-view]=\"!isPublisherSmall\"\n #publisherContainer\n (dblclick)=\"onDoubleClick()\"\n ></div>\n\n <div class=\"controls-container\">\n <button class=\"btn swap-btn\" (click)=\"toggleSwap()\" title=\"Cambiar vista\">\n <i class=\"fa fa-refresh\"></i>\n </button>\n <button\n class=\"btn pip-btn\"\n (click)=\"minimize()\"\n title=\"Minimizar (Picture in Picture)\"\n >\n <i class=\"fa fa-compress\"></i>\n </button>\n <button\n class=\"btn mute-btn\"\n [class.muted]=\"isMuted\"\n (click)=\"toggleMute()\"\n [title]=\"isMuted ? 'Activar micr\u00F3fono' : 'Silenciar micr\u00F3fono'\"\n >\n <i\n class=\"fa\"\n [class.fa-microphone]=\"!isMuted\"\n [class.fa-microphone-slash]=\"isMuted\"\n ></i>\n </button>\n <button class=\"btn hangup-btn\" (click)=\"hangUp()\" title=\"Colgar\">\n <i class=\"fa fa-phone\" style=\"transform: rotate(135deg)\"></i>\n </button>\n </div>\n</div>\n", styles: [".tas-videocall-container{position:relative;width:100vw;height:100vh;background-color:#000;overflow:hidden}.tas-videocall-container ::ng-deep .OT_edge-bar-item,.tas-videocall-container ::ng-deep .OT_mute,.tas-videocall-container ::ng-deep .OT_audio-level-meter,.tas-videocall-container ::ng-deep .OT_bar,.tas-videocall-container ::ng-deep .OT_name{display:none!important}.tas-videocall-container .subscriber-view{width:100%;height:100%;z-index:1}.tas-videocall-container .publisher-view{position:absolute;top:20px;right:20px;width:200px;height:150px;z-index:2;border:2px solid #fff;border-radius:8px;background-color:#333}.tas-videocall-container .controls-container{display:flex;flex-direction:row;gap:20px;position:absolute;bottom:30px;left:50%;transform:translate(-50%);z-index:3;background-color:#00000080;padding:15px 25px;border-radius:50px;backdrop-filter:blur(5px)}.tas-videocall-container .controls-container .hangup-btn,.tas-videocall-container .controls-container .swap-btn,.tas-videocall-container .controls-container .pip-btn,.tas-videocall-container .controls-container .mute-btn{width:60px;height:60px;border-radius:50%;display:flex;align-items:center;justify-content:center;font-size:24px;border:none;box-shadow:0 4px 6px #0000004d;transition:all .2s ease}.tas-videocall-container .controls-container .hangup-btn i,.tas-videocall-container .controls-container .swap-btn i,.tas-videocall-container .controls-container .pip-btn i,.tas-videocall-container .controls-container .mute-btn i{color:#fff}.tas-videocall-container .controls-container .hangup-btn{background:#dc3545}.tas-videocall-container .controls-container .hangup-btn:hover{background:#c82333;transform:scale(1.05)}.tas-videocall-container .controls-container .swap-btn{background:rgba(255,255,255,.2)}.tas-videocall-container .controls-container .swap-btn:hover{background:rgba(255,255,255,.35);transform:scale(1.05)}.tas-videocall-container .controls-container .pip-btn{background:rgba(255,255,255,.2)}.tas-videocall-container .controls-container .pip-btn:hover{background:rgba(255,255,255,.35);transform:scale(1.05)}.tas-videocall-container .controls-container .mute-btn{background:rgba(255,255,255,.2)}.tas-videocall-container .controls-container .mute-btn:hover{background:rgba(255,255,255,.35);transform:scale(1.05)}.tas-videocall-container .controls-container .mute-btn.muted{background:#f39c12}.tas-videocall-container .controls-container .mute-btn.muted:hover{background:#e67e22}\n"] }]
429
+ }], ctorParameters: function () { return [{ type: i1.NgbActiveModal }, { type: TasService }]; }, propDecorators: { sessionId: [{
430
+ type: Input
431
+ }], token: [{
432
+ type: Input
433
+ }], isReturningFromPip: [{
434
+ type: Input
435
+ }], publisherContainer: [{
436
+ type: ViewChild,
437
+ args: ["publisherContainer"]
438
+ }], subscriberContainer: [{
439
+ type: ViewChild,
440
+ args: ["subscriberContainer"]
441
+ }] } });
442
+
443
+ class TasButtonComponent {
444
+ constructor(tasService, modalService, userDataProvider) {
445
+ this.tasService = tasService;
446
+ this.modalService = modalService;
447
+ this.userDataProvider = userDataProvider;
448
+ this.isLoading = false;
449
+ this.userData = null;
450
+ this.tenantId = null;
451
+ this.subscriptions = new Subscription();
452
+ this.currentModalRef = null;
453
+ }
454
+ ngOnInit() {
455
+ this.loadUserData();
456
+ this.setupViewModeSubscription();
457
+ }
458
+ ngOnDestroy() {
459
+ this.subscriptions.unsubscribe();
460
+ }
461
+ onClick() {
462
+ if (!this.userData || !this.tenantId) {
463
+ console.error("User data or tenant ID not available");
464
+ return;
465
+ }
466
+ this.isLoading = true;
467
+ this.subscriptions.add(this.tasService
468
+ .createRoom({
469
+ tenant: this.tenantId,
470
+ userId: this.userData.id.toString(),
471
+ product: "Uell",
472
+ record: true,
473
+ roomType: "TAS",
474
+ type: "SPONTANEOUS",
475
+ })
476
+ .pipe(switchMap((response) => {
477
+ const sessionId = response.content.sessionId;
478
+ return this.tasService
479
+ .generateToken({
480
+ sessionId: sessionId,
481
+ name: this.userData.name,
482
+ lastname: this.userData.surname,
483
+ })
484
+ .pipe(switchMap((tokenResponse) => {
485
+ return [
486
+ {
487
+ sessionId,
488
+ token: tokenResponse.content.token,
489
+ },
490
+ ];
491
+ }));
492
+ }))
493
+ .subscribe({
494
+ next: ({ sessionId, token }) => {
495
+ this.isLoading = false;
496
+ this.openVideoCallModal(sessionId, token);
497
+ },
498
+ error: (err) => {
499
+ console.error("Error starting video call:", err);
500
+ this.isLoading = false;
501
+ },
502
+ }));
503
+ }
504
+ // Private Methods
505
+ loadUserData() {
506
+ if (!this.userDataProvider) {
507
+ console.warn("TasButtonComponent: UserDataProvider not configured");
508
+ return;
509
+ }
510
+ this.userData = this.userDataProvider.getUserData();
511
+ this.tenantId = this.userDataProvider.getTenantId();
512
+ }
513
+ setupViewModeSubscription() {
514
+ this.subscriptions.add(this.tasService.viewMode$.subscribe((mode) => {
515
+ if (mode === ViewMode.FULLSCREEN &&
516
+ this.tasService.isCallActive() &&
517
+ !this.currentModalRef) {
518
+ const sessionId = this.tasService.sessionId;
519
+ const token = this.tasService.token;
520
+ if (sessionId && token) {
521
+ this.openVideoCallModal(sessionId, token, true);
522
+ }
523
+ }
524
+ }));
525
+ }
526
+ openVideoCallModal(sessionId, token, isReturningFromPip = false) {
527
+ this.currentModalRef = this.modalService.open(TasVideocallComponent, {
528
+ size: "xl",
529
+ windowClass: "tas-video-modal",
530
+ backdrop: "static",
531
+ keyboard: false,
532
+ });
533
+ this.currentModalRef.componentInstance.sessionId = sessionId;
534
+ this.currentModalRef.componentInstance.token = token;
535
+ this.currentModalRef.componentInstance.isReturningFromPip =
536
+ isReturningFromPip;
537
+ this.currentModalRef.result.then(() => {
538
+ this.currentModalRef = null;
539
+ }, () => {
540
+ this.currentModalRef = null;
541
+ });
542
+ }
543
+ }
544
+ TasButtonComponent.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "13.4.0", ngImport: i0, type: TasButtonComponent, deps: [{ token: TasService }, { token: i1.NgbModal }, { token: TAS_USER_DATA_PROVIDER, optional: true }], target: i0.ɵɵFactoryTarget.Component });
545
+ TasButtonComponent.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "12.0.0", version: "13.4.0", type: TasButtonComponent, selector: "tas-btn", ngImport: i0, template: "<button\n type=\"button\"\n class=\"btn btn-primary boton\"\n (click)=\"onClick()\"\n [disabled]=\"isLoading\"\n>\n <i class=\"fa fa-video-camera\" aria-hidden=\"true\" *ngIf=\"!isLoading\"></i>\n <span *ngIf=\"!isLoading\"> Iniciar TAS</span>\n <span *ngIf=\"isLoading\"> Processing...</span>\n</button>\n", styles: [":host{display:inline-block}.boton{background-color:#ee316b!important;color:#fff!important;margin-right:24px}.boton:disabled{background-color:#ccc!important;border-color:#ccc!important;cursor:not-allowed}.boton i{margin-right:5px}\n"], directives: [{ type: i3.NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }] });
546
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "13.4.0", ngImport: i0, type: TasButtonComponent, decorators: [{
547
+ type: Component,
548
+ args: [{ selector: "tas-btn", template: "<button\n type=\"button\"\n class=\"btn btn-primary boton\"\n (click)=\"onClick()\"\n [disabled]=\"isLoading\"\n>\n <i class=\"fa fa-video-camera\" aria-hidden=\"true\" *ngIf=\"!isLoading\"></i>\n <span *ngIf=\"!isLoading\"> Iniciar TAS</span>\n <span *ngIf=\"isLoading\"> Processing...</span>\n</button>\n", styles: [":host{display:inline-block}.boton{background-color:#ee316b!important;color:#fff!important;margin-right:24px}.boton:disabled{background-color:#ccc!important;border-color:#ccc!important;cursor:not-allowed}.boton i{margin-right:5px}\n"] }]
549
+ }], ctorParameters: function () {
550
+ return [{ type: TasService }, { type: i1.NgbModal }, { type: undefined, decorators: [{
551
+ type: Optional
552
+ }, {
553
+ type: Inject,
554
+ args: [TAS_USER_DATA_PROVIDER]
555
+ }] }];
556
+ } });
557
+
558
+ class TasFloatingCallComponent {
559
+ constructor(tasService) {
560
+ this.tasService = tasService;
561
+ this.isVisible = false;
562
+ this.isMuted = false;
563
+ this.subscriptions = new Subscription();
564
+ }
565
+ ngOnInit() {
566
+ this.setupSubscriptions();
567
+ }
568
+ ngOnDestroy() {
569
+ this.subscriptions.unsubscribe();
570
+ interact(".tas-floating-container").unset();
571
+ }
572
+ // Public Methods
573
+ onExpand() {
574
+ this.tasService.exitPipMode();
575
+ }
576
+ onHangUp() {
577
+ this.tasService.disconnectSession();
578
+ }
579
+ toggleMute() {
580
+ this.tasService.toggleMute();
581
+ }
582
+ // Private Methods
583
+ setupSubscriptions() {
584
+ this.subscriptions.add(this.tasService.callState$.subscribe((state) => {
585
+ if (state === CallState.DISCONNECTED) {
586
+ this.isVisible = false;
587
+ }
588
+ }));
589
+ this.subscriptions.add(this.tasService.viewMode$.subscribe((mode) => {
590
+ this.isVisible =
591
+ mode === ViewMode.PIP && this.tasService.isCallActive();
592
+ if (this.isVisible) {
593
+ setTimeout(() => this.initInteract(), 100);
594
+ }
595
+ }));
596
+ this.subscriptions.add(this.tasService.isMuted$.subscribe((muted) => {
597
+ this.isMuted = muted;
598
+ }));
599
+ }
600
+ initInteract() {
601
+ interact(".tas-floating-container").unset();
602
+ interact(".tas-floating-container").draggable({
603
+ inertia: true,
604
+ modifiers: [
605
+ interact.modifiers.restrictRect({
606
+ restriction: "body",
607
+ endOnly: true,
608
+ }),
609
+ ],
610
+ autoScroll: false,
611
+ listeners: {
612
+ move: (event) => {
613
+ const target = event.target;
614
+ const x = (parseFloat(target.getAttribute("data-x")) || 0) + event.dx;
615
+ const y = (parseFloat(target.getAttribute("data-y")) || 0) + event.dy;
616
+ target.style.transform = `translate(${x}px, ${y}px)`;
617
+ target.setAttribute("data-x", String(x));
618
+ target.setAttribute("data-y", String(y));
619
+ },
620
+ },
621
+ });
622
+ }
623
+ }
624
+ TasFloatingCallComponent.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "13.4.0", ngImport: i0, type: TasFloatingCallComponent, deps: [{ token: TasService }], target: i0.ɵɵFactoryTarget.Component });
625
+ TasFloatingCallComponent.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "12.0.0", version: "13.4.0", type: TasFloatingCallComponent, selector: "tas-floating-call", ngImport: i0, template: "<div class=\"tas-floating-container\" [class.visible]=\"isVisible\">\n <div class=\"floating-content\">\n <div id=\"pip-main-video\" class=\"pip-main-video\"></div>\n\n <div class=\"floating-controls\">\n <button\n class=\"action-btn expand-btn\"\n (click)=\"onExpand()\"\n title=\"Expandir a pantalla completa\"\n >\n <i class=\"fa fa-expand\"></i>\n </button>\n <button\n class=\"action-btn mute-btn\"\n [class.muted]=\"isMuted\"\n (click)=\"toggleMute()\"\n [title]=\"isMuted ? 'Activar micr\u00F3fono' : 'Silenciar micr\u00F3fono'\"\n >\n <i\n class=\"fa\"\n [class.fa-microphone]=\"!isMuted\"\n [class.fa-microphone-slash]=\"isMuted\"\n ></i>\n </button>\n <button\n class=\"action-btn hangup-btn\"\n (click)=\"onHangUp()\"\n title=\"Colgar llamada\"\n >\n <i class=\"fa fa-phone\" style=\"transform: rotate(135deg)\"></i>\n </button>\n </div>\n </div>\n</div>\n", styles: [".tas-floating-container{position:fixed;bottom:20px;right:20px;width:280px;height:180px;background:#000;border-radius:12px;box-shadow:0 8px 32px #00000080;z-index:9999;overflow:hidden;touch-action:none;-webkit-user-select:none;user-select:none;transition:opacity .3s ease,visibility .3s ease;opacity:0;visibility:hidden;pointer-events:none}.tas-floating-container.visible{opacity:1;visibility:visible;pointer-events:auto}.floating-content{position:relative;width:100%;height:100%;overflow:hidden}.pip-main-video{position:absolute;top:0;left:0;width:100%;height:100%;background:#000}.pip-main-video ::ng-deep video{width:100%;height:100%;object-fit:cover}.pip-main-video ::ng-deep .OT_subscriber,.pip-main-video ::ng-deep .OT_publisher{width:100%!important;height:100%!important}.pip-main-video ::ng-deep .OT_edge-bar-item,.pip-main-video ::ng-deep .OT_mute,.pip-main-video ::ng-deep .OT_audio-level-meter,.pip-main-video ::ng-deep .OT_bar,.pip-main-video ::ng-deep .OT_name{display:none!important}.floating-controls{position:absolute;bottom:10px;left:50%;transform:translate(-50%);display:flex;gap:12px;padding:6px 14px;background:rgba(0,0,0,.7);border-radius:24px;backdrop-filter:blur(8px)}.action-btn{width:32px;height:32px;border:none;border-radius:50%;cursor:pointer;display:flex;align-items:center;justify-content:center;font-size:13px;transition:all .2s ease}.action-btn.expand-btn{background:rgba(255,255,255,.2);color:#fff}.action-btn.expand-btn:hover{background:rgba(255,255,255,.35);transform:scale(1.1)}.action-btn.mute-btn{background:rgba(255,255,255,.2);color:#fff}.action-btn.mute-btn:hover{background:rgba(255,255,255,.35);transform:scale(1.1)}.action-btn.mute-btn.muted{background:#f39c12;color:#fff}.action-btn.mute-btn.muted:hover{background:#e67e22}.action-btn.hangup-btn{background:#dc3545;color:#fff}.action-btn.hangup-btn:hover{background:#c82333;transform:scale(1.1)}\n"] });
626
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "13.4.0", ngImport: i0, type: TasFloatingCallComponent, decorators: [{
627
+ type: Component,
628
+ args: [{ selector: "tas-floating-call", template: "<div class=\"tas-floating-container\" [class.visible]=\"isVisible\">\n <div class=\"floating-content\">\n <div id=\"pip-main-video\" class=\"pip-main-video\"></div>\n\n <div class=\"floating-controls\">\n <button\n class=\"action-btn expand-btn\"\n (click)=\"onExpand()\"\n title=\"Expandir a pantalla completa\"\n >\n <i class=\"fa fa-expand\"></i>\n </button>\n <button\n class=\"action-btn mute-btn\"\n [class.muted]=\"isMuted\"\n (click)=\"toggleMute()\"\n [title]=\"isMuted ? 'Activar micr\u00F3fono' : 'Silenciar micr\u00F3fono'\"\n >\n <i\n class=\"fa\"\n [class.fa-microphone]=\"!isMuted\"\n [class.fa-microphone-slash]=\"isMuted\"\n ></i>\n </button>\n <button\n class=\"action-btn hangup-btn\"\n (click)=\"onHangUp()\"\n title=\"Colgar llamada\"\n >\n <i class=\"fa fa-phone\" style=\"transform: rotate(135deg)\"></i>\n </button>\n </div>\n </div>\n</div>\n", styles: [".tas-floating-container{position:fixed;bottom:20px;right:20px;width:280px;height:180px;background:#000;border-radius:12px;box-shadow:0 8px 32px #00000080;z-index:9999;overflow:hidden;touch-action:none;-webkit-user-select:none;user-select:none;transition:opacity .3s ease,visibility .3s ease;opacity:0;visibility:hidden;pointer-events:none}.tas-floating-container.visible{opacity:1;visibility:visible;pointer-events:auto}.floating-content{position:relative;width:100%;height:100%;overflow:hidden}.pip-main-video{position:absolute;top:0;left:0;width:100%;height:100%;background:#000}.pip-main-video ::ng-deep video{width:100%;height:100%;object-fit:cover}.pip-main-video ::ng-deep .OT_subscriber,.pip-main-video ::ng-deep .OT_publisher{width:100%!important;height:100%!important}.pip-main-video ::ng-deep .OT_edge-bar-item,.pip-main-video ::ng-deep .OT_mute,.pip-main-video ::ng-deep .OT_audio-level-meter,.pip-main-video ::ng-deep .OT_bar,.pip-main-video ::ng-deep .OT_name{display:none!important}.floating-controls{position:absolute;bottom:10px;left:50%;transform:translate(-50%);display:flex;gap:12px;padding:6px 14px;background:rgba(0,0,0,.7);border-radius:24px;backdrop-filter:blur(8px)}.action-btn{width:32px;height:32px;border:none;border-radius:50%;cursor:pointer;display:flex;align-items:center;justify-content:center;font-size:13px;transition:all .2s ease}.action-btn.expand-btn{background:rgba(255,255,255,.2);color:#fff}.action-btn.expand-btn:hover{background:rgba(255,255,255,.35);transform:scale(1.1)}.action-btn.mute-btn{background:rgba(255,255,255,.2);color:#fff}.action-btn.mute-btn:hover{background:rgba(255,255,255,.35);transform:scale(1.1)}.action-btn.mute-btn.muted{background:#f39c12;color:#fff}.action-btn.mute-btn.muted:hover{background:#e67e22}.action-btn.hangup-btn{background:#dc3545;color:#fff}.action-btn.hangup-btn:hover{background:#c82333;transform:scale(1.1)}\n"] }]
629
+ }], ctorParameters: function () { return [{ type: TasService }]; } });
630
+
631
+ class TasModule {
632
+ /**
633
+ * Use this method in your app module to configure the TAS SDK
634
+ *
635
+ * @example
636
+ * ```typescript
637
+ * TasModule.forRoot({
638
+ * config: {
639
+ * tokBoxApiKey: environment.tokBox,
640
+ * apiBaseUrl: environment.apiUrl
641
+ * },
642
+ * httpClient: MyHttpClientAdapter,
643
+ * userDataProvider: MyUserDataProvider
644
+ * })
645
+ * ```
646
+ */
647
+ static forRoot(moduleConfig) {
648
+ const providers = [
649
+ {
650
+ provide: TAS_CONFIG,
651
+ useValue: moduleConfig.config,
652
+ },
653
+ ];
654
+ if (moduleConfig.httpClient) {
655
+ providers.push({
656
+ provide: TAS_HTTP_CLIENT,
657
+ useClass: moduleConfig.httpClient,
658
+ });
659
+ }
660
+ if (moduleConfig.userDataProvider) {
661
+ providers.push({
662
+ provide: TAS_USER_DATA_PROVIDER,
663
+ useClass: moduleConfig.userDataProvider,
664
+ });
665
+ }
666
+ return {
667
+ ngModule: TasModule,
668
+ providers,
669
+ };
670
+ }
671
+ }
672
+ TasModule.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "13.4.0", ngImport: i0, type: TasModule, deps: [], target: i0.ɵɵFactoryTarget.NgModule });
673
+ TasModule.ɵmod = i0.ɵɵngDeclareNgModule({ minVersion: "12.0.0", version: "13.4.0", ngImport: i0, type: TasModule, declarations: [TasButtonComponent,
674
+ TasVideocallComponent,
675
+ TasFloatingCallComponent], imports: [CommonModule, NgbModalModule], exports: [TasButtonComponent,
676
+ TasVideocallComponent,
677
+ TasFloatingCallComponent] });
678
+ TasModule.ɵinj = i0.ɵɵngDeclareInjector({ minVersion: "12.0.0", version: "13.4.0", ngImport: i0, type: TasModule, imports: [[CommonModule, NgbModalModule]] });
679
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "13.4.0", ngImport: i0, type: TasModule, decorators: [{
680
+ type: NgModule,
681
+ args: [{
682
+ declarations: [
683
+ TasButtonComponent,
684
+ TasVideocallComponent,
685
+ TasFloatingCallComponent,
686
+ ],
687
+ imports: [CommonModule, NgbModalModule],
688
+ exports: [
689
+ TasButtonComponent,
690
+ TasVideocallComponent,
691
+ TasFloatingCallComponent,
692
+ ],
693
+ }]
694
+ }] });
695
+
696
+ /*
697
+ * Public API Surface of tas-uell-sdk
698
+ */
699
+
700
+ /**
701
+ * Generated bundle index. Do not edit.
702
+ */
703
+
704
+ export { CallState, TAS_CONFIG, TAS_HTTP_CLIENT, TAS_USER_DATA_PROVIDER, TasButtonComponent, TasFloatingCallComponent, TasModule, TasService, TasVideocallComponent, ViewMode };
705
+ //# sourceMappingURL=tas-uell-sdk.mjs.map