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,697 @@
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
+ const subscriber = this.session?.subscribe(event.stream, subscriberElement, {
164
+ insertMode: "append",
165
+ width: "100%",
166
+ height: "100%",
167
+ }, (error) => {
168
+ if (error) {
169
+ console.error("Error subscribing to stream:", error);
170
+ }
171
+ });
172
+ if (subscriber) {
173
+ this.subscribers.push(subscriber);
174
+ }
175
+ });
176
+ // Handle streams ending (remote participants leaving)
177
+ this.session.on("streamDestroyed", (event) => {
178
+ this.subscribers = this.subscribers.filter((sub) => sub.stream?.streamId !== event.stream.streamId);
179
+ });
180
+ // Handle session disconnection
181
+ this.session.on("sessionDisconnected", () => {
182
+ this.callStateSubject.next(CallState.DISCONNECTED);
183
+ });
184
+ // Connect to session
185
+ this.session.connect(token, (error) => {
186
+ if (error) {
187
+ console.error("Error connecting to session:", error);
188
+ this.callStateSubject.next(CallState.ERROR);
189
+ reject(error);
190
+ return;
191
+ }
192
+ // Initialize publisher (local video)
193
+ this.publisher = OT.initPublisher(publisherElement, {
194
+ insertMode: "append",
195
+ width: "100%",
196
+ height: "100%",
197
+ }, (err) => {
198
+ if (err) {
199
+ console.error("Error initializing publisher:", err);
200
+ this.callStateSubject.next(CallState.ERROR);
201
+ reject(err);
202
+ return;
203
+ }
204
+ // Publish to session
205
+ this.session?.publish(this.publisher, (pubErr) => {
206
+ if (pubErr) {
207
+ console.error("Error publishing stream:", pubErr);
208
+ this.callStateSubject.next(CallState.ERROR);
209
+ reject(pubErr);
210
+ }
211
+ else {
212
+ this.callStateSubject.next(CallState.CONNECTED);
213
+ resolve();
214
+ }
215
+ });
216
+ });
217
+ });
218
+ });
219
+ }
220
+ // Video Element Movement Methods
221
+ movePublisherTo(newContainerId) {
222
+ if (!this.publisher)
223
+ return;
224
+ const publisherElement = this.publisher.element;
225
+ const newContainer = document.getElementById(newContainerId);
226
+ if (publisherElement && newContainer) {
227
+ newContainer.appendChild(publisherElement);
228
+ }
229
+ }
230
+ moveSubscribersTo(newContainerId) {
231
+ const newContainer = document.getElementById(newContainerId);
232
+ if (!newContainer)
233
+ return;
234
+ this.subscribers.forEach((subscriber) => {
235
+ const subscriberElement = subscriber.element;
236
+ if (subscriberElement) {
237
+ newContainer.appendChild(subscriberElement);
238
+ }
239
+ });
240
+ }
241
+ /**
242
+ * Moves the main video (subscriber if available, otherwise publisher) to a container.
243
+ * Used for PiP mode to show only one video.
244
+ */
245
+ moveMainVideoTo(newContainerId) {
246
+ const newContainer = document.getElementById(newContainerId);
247
+ if (!newContainer)
248
+ return;
249
+ // Prefer remote video (subscriber) as main
250
+ if (this.subscribers.length > 0 && this.subscribers[0].element) {
251
+ newContainer.appendChild(this.subscribers[0].element);
252
+ return;
253
+ }
254
+ // Fall back to local video (publisher)
255
+ if (this.publisher?.element) {
256
+ newContainer.appendChild(this.publisher.element);
257
+ }
258
+ }
259
+ /**
260
+ * Moves videos back to fullscreen containers
261
+ */
262
+ moveVideosToFullscreen() {
263
+ this.moveSubscribersTo("subscriber-container");
264
+ this.movePublisherTo("publisher-container");
265
+ }
266
+ }
267
+ 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 });
268
+ TasService.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "13.4.0", ngImport: i0, type: TasService, providedIn: "root" });
269
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "13.4.0", ngImport: i0, type: TasService, decorators: [{
270
+ type: Injectable,
271
+ args: [{ providedIn: "root" }]
272
+ }], ctorParameters: function () { return [{ type: undefined, decorators: [{
273
+ type: Optional
274
+ }, {
275
+ type: Inject,
276
+ args: [TAS_CONFIG]
277
+ }] }, { type: undefined, decorators: [{
278
+ type: Optional
279
+ }, {
280
+ type: Inject,
281
+ args: [TAS_HTTP_CLIENT]
282
+ }] }]; } });
283
+
284
+ class TasVideocallComponent {
285
+ constructor(activeModal, tasService) {
286
+ this.activeModal = activeModal;
287
+ this.tasService = tasService;
288
+ this.isReturningFromPip = false;
289
+ this.isPublisherSmall = true;
290
+ this.callState = CallState.IDLE;
291
+ this.isMuted = false;
292
+ this.subscriptions = new Subscription();
293
+ }
294
+ ngOnInit() {
295
+ this.setupSubscriptions();
296
+ this.initializeCall();
297
+ }
298
+ ngAfterViewInit() {
299
+ this.initInteract();
300
+ }
301
+ ngOnDestroy() {
302
+ this.subscriptions.unsubscribe();
303
+ if (!this.tasService.isPipMode()) {
304
+ this.tasService.disconnectSession();
305
+ }
306
+ interact(".publisher-view").unset();
307
+ }
308
+ // Public Methods
309
+ hangUp() {
310
+ this.tasService.disconnectSession();
311
+ }
312
+ toggleMute() {
313
+ this.tasService.toggleMute();
314
+ }
315
+ minimize() {
316
+ this.tasService.moveMainVideoTo("pip-main-video");
317
+ setTimeout(() => this.tasService.enterPipMode(), 50);
318
+ }
319
+ toggleSwap() {
320
+ this.isPublisherSmall = !this.isPublisherSmall;
321
+ this.resetVideoPositions();
322
+ setTimeout(() => this.initInteract());
323
+ }
324
+ onDoubleClick() {
325
+ this.toggleSwap();
326
+ }
327
+ // Private Methods
328
+ setupSubscriptions() {
329
+ this.subscriptions.add(this.tasService.callState$.subscribe((state) => {
330
+ this.callState = state;
331
+ if (state === CallState.DISCONNECTED) {
332
+ this.activeModal.close("hangup");
333
+ }
334
+ }));
335
+ this.subscriptions.add(this.tasService.viewMode$.subscribe((mode) => {
336
+ if (mode === ViewMode.PIP) {
337
+ this.activeModal.close("pip");
338
+ }
339
+ }));
340
+ this.subscriptions.add(this.tasService.isMuted$.subscribe((muted) => {
341
+ this.isMuted = muted;
342
+ }));
343
+ }
344
+ initializeCall() {
345
+ if (this.isReturningFromPip) {
346
+ setTimeout(() => this.tasService.moveVideosToFullscreen(), 100);
347
+ }
348
+ else if (this.sessionId && this.token) {
349
+ this.tasService
350
+ .connectSession(this.sessionId, this.token, "publisher-container", "subscriber-container")
351
+ .catch((err) => {
352
+ console.error("Error connecting to video call:", err);
353
+ });
354
+ }
355
+ }
356
+ resetVideoPositions() {
357
+ const publisherEl = this.publisherContainer?.nativeElement;
358
+ const subscriberEl = this.subscriberContainer?.nativeElement;
359
+ if (publisherEl) {
360
+ publisherEl.removeAttribute("style");
361
+ publisherEl.removeAttribute("data-x");
362
+ publisherEl.removeAttribute("data-y");
363
+ }
364
+ if (subscriberEl) {
365
+ subscriberEl.removeAttribute("style");
366
+ subscriberEl.removeAttribute("data-x");
367
+ subscriberEl.removeAttribute("data-y");
368
+ }
369
+ }
370
+ initInteract() {
371
+ interact(".publisher-view").unset();
372
+ interact(".publisher-view")
373
+ .draggable({
374
+ inertia: true,
375
+ modifiers: [
376
+ interact.modifiers.restrictRect({
377
+ restriction: "parent",
378
+ endOnly: true,
379
+ }),
380
+ ],
381
+ autoScroll: true,
382
+ listeners: {
383
+ move: (event) => {
384
+ const target = event.target;
385
+ const x = (parseFloat(target.getAttribute("data-x")) || 0) + event.dx;
386
+ const y = (parseFloat(target.getAttribute("data-y")) || 0) + event.dy;
387
+ target.style.transform = `translate(${x}px, ${y}px)`;
388
+ target.setAttribute("data-x", String(x));
389
+ target.setAttribute("data-y", String(y));
390
+ },
391
+ },
392
+ })
393
+ .resizable({
394
+ edges: { left: false, right: true, bottom: true, top: false },
395
+ listeners: {
396
+ move: (event) => {
397
+ const target = event.target;
398
+ let x = parseFloat(target.getAttribute("data-x")) || 0;
399
+ let y = parseFloat(target.getAttribute("data-y")) || 0;
400
+ target.style.width = `${event.rect.width}px`;
401
+ target.style.height = `${event.rect.height}px`;
402
+ x += event.deltaRect.left;
403
+ y += event.deltaRect.top;
404
+ target.style.transform = `translate(${x}px, ${y}px)`;
405
+ target.setAttribute("data-x", String(x));
406
+ target.setAttribute("data-y", String(y));
407
+ },
408
+ },
409
+ modifiers: [
410
+ interact.modifiers.restrictEdges({ outer: "parent" }),
411
+ interact.modifiers.restrictSize({ min: { width: 150, height: 100 } }),
412
+ interact.modifiers.aspectRatio({ ratio: "preserve" }),
413
+ ],
414
+ inertia: true,
415
+ });
416
+ }
417
+ }
418
+ 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 });
419
+ 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"] });
420
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "13.4.0", ngImport: i0, type: TasVideocallComponent, decorators: [{
421
+ type: Component,
422
+ 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"] }]
423
+ }], ctorParameters: function () { return [{ type: i1.NgbActiveModal }, { type: TasService }]; }, propDecorators: { sessionId: [{
424
+ type: Input
425
+ }], token: [{
426
+ type: Input
427
+ }], isReturningFromPip: [{
428
+ type: Input
429
+ }], publisherContainer: [{
430
+ type: ViewChild,
431
+ args: ["publisherContainer"]
432
+ }], subscriberContainer: [{
433
+ type: ViewChild,
434
+ args: ["subscriberContainer"]
435
+ }] } });
436
+
437
+ class TasButtonComponent {
438
+ constructor(tasService, modalService, userDataProvider) {
439
+ this.tasService = tasService;
440
+ this.modalService = modalService;
441
+ this.userDataProvider = userDataProvider;
442
+ this.isLoading = false;
443
+ this.userData = null;
444
+ this.tenantId = null;
445
+ this.subscriptions = new Subscription();
446
+ this.currentModalRef = null;
447
+ }
448
+ ngOnInit() {
449
+ this.loadUserData();
450
+ this.setupViewModeSubscription();
451
+ }
452
+ ngOnDestroy() {
453
+ this.subscriptions.unsubscribe();
454
+ }
455
+ onClick() {
456
+ if (!this.userData || !this.tenantId) {
457
+ console.error("User data or tenant ID not available");
458
+ return;
459
+ }
460
+ this.isLoading = true;
461
+ this.subscriptions.add(this.tasService
462
+ .createRoom({
463
+ tenant: this.tenantId,
464
+ userId: this.userData.id.toString(),
465
+ product: "Uell",
466
+ record: true,
467
+ roomType: "TAS",
468
+ type: "SPONTANEOUS",
469
+ })
470
+ .pipe(switchMap((response) => {
471
+ const sessionId = response.content.sessionId;
472
+ return this.tasService
473
+ .generateToken({
474
+ sessionId: sessionId,
475
+ name: this.userData.name,
476
+ lastname: this.userData.surname,
477
+ })
478
+ .pipe(switchMap((tokenResponse) => {
479
+ return [
480
+ {
481
+ sessionId,
482
+ token: tokenResponse.content.token,
483
+ },
484
+ ];
485
+ }));
486
+ }))
487
+ .subscribe({
488
+ next: ({ sessionId, token }) => {
489
+ this.isLoading = false;
490
+ this.openVideoCallModal(sessionId, token);
491
+ },
492
+ error: (err) => {
493
+ console.error("Error starting video call:", err);
494
+ this.isLoading = false;
495
+ },
496
+ }));
497
+ }
498
+ // Private Methods
499
+ loadUserData() {
500
+ if (!this.userDataProvider) {
501
+ console.warn("TasButtonComponent: UserDataProvider not configured");
502
+ return;
503
+ }
504
+ this.userData = this.userDataProvider.getUserData();
505
+ this.tenantId = this.userDataProvider.getTenantId();
506
+ }
507
+ setupViewModeSubscription() {
508
+ this.subscriptions.add(this.tasService.viewMode$.subscribe((mode) => {
509
+ if (mode === ViewMode.FULLSCREEN &&
510
+ this.tasService.isCallActive() &&
511
+ !this.currentModalRef) {
512
+ const sessionId = this.tasService.sessionId;
513
+ const token = this.tasService.token;
514
+ if (sessionId && token) {
515
+ this.openVideoCallModal(sessionId, token, true);
516
+ }
517
+ }
518
+ }));
519
+ }
520
+ openVideoCallModal(sessionId, token, isReturningFromPip = false) {
521
+ this.currentModalRef = this.modalService.open(TasVideocallComponent, {
522
+ size: "xl",
523
+ windowClass: "tas-video-modal",
524
+ backdrop: "static",
525
+ keyboard: false,
526
+ });
527
+ this.currentModalRef.componentInstance.sessionId = sessionId;
528
+ this.currentModalRef.componentInstance.token = token;
529
+ this.currentModalRef.componentInstance.isReturningFromPip =
530
+ isReturningFromPip;
531
+ this.currentModalRef.result.then(() => {
532
+ this.currentModalRef = null;
533
+ }, () => {
534
+ this.currentModalRef = null;
535
+ });
536
+ }
537
+ }
538
+ 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 });
539
+ 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"] }] });
540
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "13.4.0", ngImport: i0, type: TasButtonComponent, decorators: [{
541
+ type: Component,
542
+ 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"] }]
543
+ }], ctorParameters: function () { return [{ type: TasService }, { type: i1.NgbModal }, { type: undefined, decorators: [{
544
+ type: Optional
545
+ }, {
546
+ type: Inject,
547
+ args: [TAS_USER_DATA_PROVIDER]
548
+ }] }]; } });
549
+
550
+ class TasFloatingCallComponent {
551
+ constructor(tasService) {
552
+ this.tasService = tasService;
553
+ this.isVisible = false;
554
+ this.isMuted = false;
555
+ this.subscriptions = new Subscription();
556
+ }
557
+ ngOnInit() {
558
+ this.setupSubscriptions();
559
+ }
560
+ ngOnDestroy() {
561
+ this.subscriptions.unsubscribe();
562
+ interact(".tas-floating-container").unset();
563
+ }
564
+ // Public Methods
565
+ onExpand() {
566
+ this.tasService.exitPipMode();
567
+ }
568
+ onHangUp() {
569
+ this.tasService.disconnectSession();
570
+ }
571
+ toggleMute() {
572
+ this.tasService.toggleMute();
573
+ }
574
+ // Private Methods
575
+ setupSubscriptions() {
576
+ this.subscriptions.add(this.tasService.callState$.subscribe((state) => {
577
+ if (state === CallState.DISCONNECTED) {
578
+ this.isVisible = false;
579
+ }
580
+ }));
581
+ this.subscriptions.add(this.tasService.viewMode$.subscribe((mode) => {
582
+ this.isVisible =
583
+ mode === ViewMode.PIP && this.tasService.isCallActive();
584
+ if (this.isVisible) {
585
+ setTimeout(() => this.initInteract(), 100);
586
+ }
587
+ }));
588
+ this.subscriptions.add(this.tasService.isMuted$.subscribe((muted) => {
589
+ this.isMuted = muted;
590
+ }));
591
+ }
592
+ initInteract() {
593
+ interact(".tas-floating-container").unset();
594
+ interact(".tas-floating-container").draggable({
595
+ inertia: true,
596
+ modifiers: [
597
+ interact.modifiers.restrictRect({
598
+ restriction: "body",
599
+ endOnly: true,
600
+ }),
601
+ ],
602
+ autoScroll: false,
603
+ listeners: {
604
+ move: (event) => {
605
+ const target = event.target;
606
+ const x = (parseFloat(target.getAttribute("data-x")) || 0) + event.dx;
607
+ const y = (parseFloat(target.getAttribute("data-y")) || 0) + event.dy;
608
+ target.style.transform = `translate(${x}px, ${y}px)`;
609
+ target.setAttribute("data-x", String(x));
610
+ target.setAttribute("data-y", String(y));
611
+ },
612
+ },
613
+ });
614
+ }
615
+ }
616
+ TasFloatingCallComponent.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "13.4.0", ngImport: i0, type: TasFloatingCallComponent, deps: [{ token: TasService }], target: i0.ɵɵFactoryTarget.Component });
617
+ 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"] });
618
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "13.4.0", ngImport: i0, type: TasFloatingCallComponent, decorators: [{
619
+ type: Component,
620
+ 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"] }]
621
+ }], ctorParameters: function () { return [{ type: TasService }]; } });
622
+
623
+ class TasModule {
624
+ /**
625
+ * Use this method in your app module to configure the TAS SDK
626
+ *
627
+ * @example
628
+ * ```typescript
629
+ * TasModule.forRoot({
630
+ * config: {
631
+ * tokBoxApiKey: environment.tokBox,
632
+ * apiBaseUrl: environment.apiUrl
633
+ * },
634
+ * httpClient: MyHttpClientAdapter,
635
+ * userDataProvider: MyUserDataProvider
636
+ * })
637
+ * ```
638
+ */
639
+ static forRoot(moduleConfig) {
640
+ const providers = [
641
+ {
642
+ provide: TAS_CONFIG,
643
+ useValue: moduleConfig.config,
644
+ },
645
+ ];
646
+ if (moduleConfig.httpClient) {
647
+ providers.push({
648
+ provide: TAS_HTTP_CLIENT,
649
+ useClass: moduleConfig.httpClient,
650
+ });
651
+ }
652
+ if (moduleConfig.userDataProvider) {
653
+ providers.push({
654
+ provide: TAS_USER_DATA_PROVIDER,
655
+ useClass: moduleConfig.userDataProvider,
656
+ });
657
+ }
658
+ return {
659
+ ngModule: TasModule,
660
+ providers,
661
+ };
662
+ }
663
+ }
664
+ TasModule.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "13.4.0", ngImport: i0, type: TasModule, deps: [], target: i0.ɵɵFactoryTarget.NgModule });
665
+ TasModule.ɵmod = i0.ɵɵngDeclareNgModule({ minVersion: "12.0.0", version: "13.4.0", ngImport: i0, type: TasModule, declarations: [TasButtonComponent,
666
+ TasVideocallComponent,
667
+ TasFloatingCallComponent], imports: [CommonModule, NgbModalModule], exports: [TasButtonComponent,
668
+ TasVideocallComponent,
669
+ TasFloatingCallComponent] });
670
+ TasModule.ɵinj = i0.ɵɵngDeclareInjector({ minVersion: "12.0.0", version: "13.4.0", ngImport: i0, type: TasModule, imports: [[CommonModule, NgbModalModule]] });
671
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "13.4.0", ngImport: i0, type: TasModule, decorators: [{
672
+ type: NgModule,
673
+ args: [{
674
+ declarations: [
675
+ TasButtonComponent,
676
+ TasVideocallComponent,
677
+ TasFloatingCallComponent,
678
+ ],
679
+ imports: [CommonModule, NgbModalModule],
680
+ exports: [
681
+ TasButtonComponent,
682
+ TasVideocallComponent,
683
+ TasFloatingCallComponent,
684
+ ],
685
+ }]
686
+ }] });
687
+
688
+ /*
689
+ * Public API Surface of tas-uell-sdk
690
+ */
691
+
692
+ /**
693
+ * Generated bundle index. Do not edit.
694
+ */
695
+
696
+ export { CallState, TAS_CONFIG, TAS_HTTP_CLIENT, TAS_USER_DATA_PROVIDER, TasButtonComponent, TasFloatingCallComponent, TasModule, TasService, TasVideocallComponent, ViewMode };
697
+ //# sourceMappingURL=tas-uell-sdk.mjs.map