Session::toJson()   A
last analyzed

Complexity

Conditions 1
Paths 1

Size

Total Lines 4
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Importance

Changes 2
Bugs 0 Features 1
Metric Value
cc 1
eloc 2
c 2
b 0
f 1
nc 1
nop 1
dl 0
loc 4
rs 10
1
<?php
2
3
namespace SquareetLabs\LaravelOpenVidu;
4
5
use Exception;
6
use GuzzleHttp\Client;
7
use GuzzleHttp\RequestOptions;
8
use Illuminate\Support\Facades\Cache;
9
use JsonSerializable;
10
use SquareetLabs\LaravelOpenVidu\Builders\ConnectionBuilder;
11
use SquareetLabs\LaravelOpenVidu\Builders\PublisherBuilder;
12
use SquareetLabs\LaravelOpenVidu\Builders\SessionPropertiesBuilder;
13
use SquareetLabs\LaravelOpenVidu\Builders\SubscriberBuilder;
14
use SquareetLabs\LaravelOpenVidu\Enums\MediaMode;
15
use SquareetLabs\LaravelOpenVidu\Enums\OpenViduRole;
16
use SquareetLabs\LaravelOpenVidu\Enums\OutputMode;
17
use SquareetLabs\LaravelOpenVidu\Enums\RecordingLayout;
18
use SquareetLabs\LaravelOpenVidu\Enums\RecordingMode;
19
use SquareetLabs\LaravelOpenVidu\Enums\Uri;
20
use SquareetLabs\LaravelOpenVidu\Exceptions\OpenViduConnectionNotFoundException;
21
use SquareetLabs\LaravelOpenVidu\Exceptions\OpenViduException;
22
use SquareetLabs\LaravelOpenVidu\Exceptions\OpenViduProblemWithBodyParameterException;
23
use SquareetLabs\LaravelOpenVidu\Exceptions\OpenViduSessionNotFoundException;
24
use SquareetLabs\LaravelOpenVidu\Exceptions\OpenViduStreamCantCloseException;
25
use SquareetLabs\LaravelOpenVidu\Exceptions\OpenViduTokenCantCreateException;
26
27
/**
28
 * Class Session
29
 * @package SquareetLabs\LaravelOpenVidu
30
 */
31
class Session implements JsonSerializable
32
{
33
    /** @var  Client */
34
    private $client;
35
36
    /** @var  string */
37
    private $sessionId;
38
39
    /** @var SessionProperties */
40
    private $properties;
41
42
    /** @var bool */
43
    private $recording;
44
45
    /** @var string */
46
    private $lastRecordingId;
47
48
    /** @var array */
49
    private $activeConnections = [];
50
51
    /** @var int */
52
    private $createdAt;
53
54
    /**
55
     * Session constructor.
56
     * @param  Client  $client
57
     * @param  string|null  $sessionId
58
     * @param  SessionProperties|null  $properties
59
     * @throws OpenViduException
60
     */
61
    public function __construct(Client $client, ?string $sessionId = null, ?SessionProperties $properties = null)
62
    {
63
        $this->client = $client;
64
        if (!$sessionId) {
65
            $this->properties = $properties ? $properties : new SessionProperties(MediaMode::ROUTED, RecordingMode::MANUAL, OutputMode::COMPOSED, RecordingLayout::BEST_FIT);
66
            $this->sessionId = $this->getSessionId();
67
        }
68
    }
69
70
    /**
71
     * @return string
72
     * @throws OpenViduException
73
     */
74
    public function getSessionId()
75
    {
76
        if (empty($this->sessionId)) {
77
            $response = $this->client->post(Uri::SESSION_URI, [
78
                RequestOptions::JSON => $this->properties->toArray()
79
            ]);
80
            switch ($response->getStatusCode()) {
81
                case 200:
82
                    return json_decode($response->getBody()->getContents())->id;
83
                case 400:
84
                    throw  new OpenViduProblemWithBodyParameterException();
85
                case 409:
86
                    return $this->properties->getCustomSessionId();
87
                default:
88
                    throw new OpenViduException("Invalid response status code " . $response->getStatusCode(), $response->getStatusCode());
89
            }
90
        } else {
91
            return $this->sessionId;
92
        }
93
    }
94
95
    /**
96
     ** Gets a new token associated to Session object with default values for
97
     * {@see TokenOptions}. This always translates into a
98
     * new request to OpenVidu Server
99
     *
100
     * @param  TokenOptions|null  $tokenOptions
101
     * @return string The generated token
102
     * @throws OpenViduException
103
     */
104
    public function generateToken(?TokenOptions $tokenOptions = null)
105
    {
106
        $this->getSessionId();
107
        try {
108
            if (!$tokenOptions) {
109
                $tokenOptions = new TokenOptions(OpenViduRole::PUBLISHER);
110
            }
111
            $response = $this->client->post(Uri::TOKEN_URI, [
112
                RequestOptions::JSON => array_merge($tokenOptions->toArray(), ['session' => $this->sessionId])
113
            ]);
114
            switch ($response->getStatusCode()) {
115
                case 200:
116
                    return json_decode($response->getBody()->getContents());
117
                case 400:
118
                    throw new OpenViduProblemWithBodyParameterException();
119
                case 404:
120
                    throw new OpenViduSessionNotFoundException();
121
                default:
122
                    throw new OpenViduException("Invalid response status code " . $response->getStatusCode(), $response->getStatusCode());
123
            }
124
        } catch (Exception $e) {
125
            throw new OpenViduTokenCantCreateException($e->getMessage(), $e);
126
        }
127
    }
128
129
    /**
130
     * Gracefully closes the Session: unpublishes all streams and evicts every
131
     * participant
132
     * @throws OpenViduException
133
     */
134
    public function close()
135
    {
136
        $response = $this->client->delete(Uri::SESSION_URI . '/' . $this->sessionId);
137
        switch ($response->getStatusCode()) {
138
            case 204:
139
                Cache::store('openvidu')->forget($this->sessionId);
140
                return true;
141
            case 404:
142
                throw new OpenViduSessionNotFoundException();
143
            default:
144
                throw new OpenViduException("Invalid response status code ".$response->getStatusCode(), $response->getStatusCode());
145
        }
146
    }
147
148
    /**
149
     * Updates every property of the Session with the current status it has in
150
     * OpenVidu Server. This is especially useful for getting the list of active
151
     * connections to the Session
152
     * ({@see getActiveConnections()}) and use
153
     * those values to call
154
     * {@see forceDisconnect(Connection)} or
155
     * {@link forceUnpublish(Publisher)}. <br>
156
     *
157
     * To update every Session object owned by OpenVidu object, call
158
     * {@see fetch()}
159
     *
160
     * @return bool true if the Session status has changed with respect to the server,
161
     * false if not. This applies to any property or sub-property of the object
162
     */
163
164
    public function fetch()
165
    {
166
        $response = $this->client->get(Uri::SESSION_URI . '/' . $this->sessionId, [
167
            'headers' => [
168
                'Content-Type' => 'application/x-www-form-urlencoded',
169
                'Accept' => 'application/json',
170
            ]
171
        ]);
172
        if ($response->getStatusCode() === 200) {
173
            $beforeJSON = $this->toJson();
174
            $this->fromJson($response->getBody()->getContents());
175
            $afterJSON = $this->toJson();
176
            if ($beforeJSON !== $afterJSON) {
177
                Cache::store('openvidu')->update($this->sessionId, $this->toJson());
178
                return true;
179
            }
180
        }
181
        return false;
182
    }
183
184
    /**
185
     * Convert the model instance to JSON.
186
     *
187
     * @param  int  $options
188
     * @return string
189
     *
190
     */
191
    public function toJson($options = 0): string
192
    {
193
        $json = json_encode($this->jsonSerialize(), $options);
194
        return $json;
195
    }
196
197
    /**
198
     * Specify data which should be serialized to JSON
199
     * @link https://php.net/manual/en/jsonserializable.jsonserialize.php
200
     * @return mixed data which can be serialized by <b>json_encode</b>,
201
     * which is a value of any type other than a resource.
202
     * @since 5.4.0
203
     */
204
    public function jsonSerialize()
205
    {
206
        return $this->toArray();
207
    }
208
209
    /**
210
     * Convert the model instance to an array.
211
     *
212
     * @return array
213
     */
214
    public function toArray(): array
215
    {
216
        $array = ['sessionId' => $this->sessionId, 'properties' => $this->properties->toArray(), 'recording' => $this->recording, 'createdAt' => $this->createdAt];
217
        foreach ($this->activeConnections as $connection) {
218
            $array['activeConnections'][] = $connection->toArray();
219
        }
220
221
        foreach ($array as $key => $value) {
222
            if (is_null($value) || $value == '') {
223
                unset($array[$key]);
224
            }
225
        }
226
        return $array;
227
    }
228
229
    /**
230
     * @param  string  $json
231
     * @return Session
232
     */
233
    public function fromJson(string $json): Session
234
    {
235
        return $this->fromArray(json_decode($json, true));
236
    }
237
238
    /**
239
     * @param  array  $sessionArray
240
     * @return Session
241
     */
242
    public function fromArray(array $sessionArray): Session
243
    {
244
        $this->sessionId = $sessionArray['sessionId'];
245
        $this->createdAt = $sessionArray['createdAt'] ?? null;
246
        $this->recording = $sessionArray['recording'] ?? null;
247
248
        if (array_key_exists('properties', $sessionArray)) {
249
            $this->properties = SessionPropertiesBuilder::build($sessionArray['properties']);
250
        }
251
252
        $this->activeConnections = [];
253
        if (array_key_exists('connections', $sessionArray)) {
254
            foreach ($sessionArray['connections']['content'] as $connection) {
255
                $publishers = [];
256
                $ensure = $connection['content'] ?? $connection;
257
                foreach ($ensure['publishers'] as $publisher) {
258
                    $publishers[] = PublisherBuilder::build($publisher);
259
                }
260
                $subscribers = [];
261
262
                foreach ($ensure['subscribers'] as $subscriber) {
263
                    $subscribers[] = SubscriberBuilder::build($subscriber);
264
                }
265
                $this->activeConnections[] = ConnectionBuilder::build($ensure, $publishers, $subscribers);
266
            }
267
        }
268
        return $this;
269
    }
270
271
    /**
272
     * Forces the user with Connection `connectionId` to leave the session. OpenVidu Browser will trigger the proper events on the client-side
273
     * (`streamDestroyed`, `connectionDestroyed`, `sessionDisconnected`) with reason set to `"forceDisconnectByServer"`
274
     *
275
     *
276
     * @param  string  $connectionId
277
     * @throws OpenViduException
278
     */
279
    public function forceDisconnect(string $connectionId)
280
    {
281
        $response = $this->client->delete(Uri::SESSION_URI . '/' . $this->sessionId . '/connection/' . $connectionId, [
282
            'headers' => [
283
                'Content-Type' => 'application/x-www-form-urlencoded',
284
                'Accept' => 'application/json',
285
            ]
286
        ]);
287
        switch ($response->getStatusCode()) {
288
            case 204:
289
                $this->leaveSession($connectionId);
290
                Cache::store('openvidu')->update($this->sessionId, $this->toJson());
291
                break;
292
            case 400:
293
                throw new OpenViduSessionNotFoundException();
294
            case 404:
295
                throw new OpenViduConnectionNotFoundException();
296
            default:
297
                throw new OpenViduException("Invalid response status code " . $response->getStatusCode(), $response->getStatusCode());
298
        }
299
    }
300
301
    /**
302
     * Get `connection` parameter from activeConnections array {@see Connection::getConnectionId()} for getting each `connectionId` property).
303
     * Remember to call {@see fetch()} before to fetch the current actual properties of the Session from OpenVidu Server
304
     * @param  string  $connectionId
305
     */
306
307
    private function leaveSession(string $connectionId)
308
    {
309
        $connectionClosed = null;
310
        $this->activeConnections = array_filter($this->activeConnections, function (Connection $connection) use (&$connectionClosed, $connectionId) {
311
            if ($connection->getConnectionId() !== $connectionId) {
312
                return true;
313
            }
314
            $connectionClosed = $connection;
315
            return false;
316
        });
317
        if ($connectionClosed != null) {
318
            foreach ($connectionClosed->getPublishers() as $publisher) {
319
                foreach ($this->activeConnections as $con) {
320
                    $con->unsubscribe($publisher->getStreamId());
321
                }
322
            }
323
        }
324
    }
325
326
327
    /**
328
     * Allows publish a Stream (For now can only be IPCAM).
329
     * Remember to call
330
     * {@see fetch()} before to fetch the current
331
     * actual properties of the Session from OpenVidu Server
332
     *
333
     * @param  IPCameraOptions  $IPCameraOptions
334
     * @return Connection
335
     * @throws OpenViduException
336
     * @throws OpenViduSessionNotFoundException
337
     */
338
    public function publish(IPCameraOptions $IPCameraOptions)
339
    {
340
        $response = $this->client->post(Uri::SESSION_URI . '/' . $this->sessionId . '/connection', [
341
            RequestOptions::JSON => array_merge($IPCameraOptions->toArray())
342
        ]);
343
        switch ($response->getStatusCode()) {
344
            case 200:
345
                return json_decode($response->getBody()->getContents());
346
            case 400:
347
                throw new OpenViduProblemWithBodyParameterException();
348
            case 404:
349
                throw new OpenViduSessionNotFoundException();
350
            default:
351
                throw new OpenViduException("Unexpected error when publishing the IP camera stream into the session.");
352
        }
353
    }
354
355
    /**
356
     * Forces some user to unpublish a Stream. OpenVidu Browser will trigger the
357
     * proper events on the client-side (<code>streamDestroyed</code>) with reason
358
     * set to "forceUnpublishByServer". <br>
359
     *
360
     * You can get <code>streamId</code> parameter with
361
     * {@see Session::getActiveConnections()} and then for
362
     * each Connection you can call
363
     * {@see  Connection::getPublishers()}. Finally
364
     * {@see Publisher::getStreamId()}) will give you the
365
     * <code>streamId</code>. Remember to call
366
     * {@see fetch()} before to fetch the current
367
     * actual properties of the Session from OpenVidu Server
368
     *
369
     * @param  string  $streamId
370
     * @return void
371
     * @throws OpenViduConnectionNotFoundException
372
     * @throws OpenViduException
373
     * @throws OpenViduSessionNotFoundException
374
     */
375
    public function forceUnpublish(string $streamId)
376
    {
377
        $response = $this->client->delete(Uri::SESSION_URI . '/' . $this->sessionId . '/stream/' . $streamId, [
378
            'headers' => [
379
                'Content-Type' => 'application/x-www-form-urlencoded',
380
                'Accept' => 'application/json',
381
            ]
382
        ]);
383
        switch ($response->getStatusCode()) {
384
            case 204:
385
                foreach ($this->activeConnections as $connection) {
386
                    $connection->unpublish($streamId);
387
                    $connection->unsubscribe($streamId);
388
                }
389
                Cache::store('openvidu')->update($this->sessionId, $this->toJson());
390
                break;
391
            case 400:
392
                throw new OpenViduSessionNotFoundException();
393
            case 404:
394
                throw new OpenViduConnectionNotFoundException();
395
            case 405:
396
                throw new OpenViduStreamCantCloseException("You cannot directly delete the stream of an IPCAM participant");
397
            default:
398
                throw new OpenViduException("Invalid response status code " . $response->getStatusCode(), $response->getStatusCode());
399
        }
400
    }
401
402
    /**
403
     * Returns the list of active connections to the session. <strong>This value
404
     * will remain unchanged since the last time method
405
     * {@see fetch()} was called</strong>.
406
     * Exceptions to this rule are:
407
     * <ul>
408
     * <li>Calling {@see Session::forceUnpublish(string)}
409
     * updates each affected Connection status</li>
410
     * <li>Calling {@see Session::forceDisconnect(string)}
411
     * updates each affected Connection status</li>
412
     * </ul>
413
     * <br>
414
     * To get the list of active connections with their current actual value, you
415
     * must call first {@see Session::fetch()} and then
416
     * {@see Session::getActiveConnections()}
417
     */
418
    public function getActiveConnections(): array
419
    {
420
        return $this->activeConnections;
421
    }
422
423
    /**
424
     * Returns whether the session is being recorded or not
425
     */
426
    public function isBeingRecorded(): bool
427
    {
428
        return !!$this->recording;
429
    }
430
431
    /**
432
     * Set value
433
     * @param  bool  $recording
434
     */
435
    public function setIsBeingRecorded(bool $recording)
436
    {
437
        $this->recording = $recording;
438
        Cache::store('openvidu')->update($this->sessionId, $this->toJson());
439
    }
440
441
    public function getLastRecordingId()
442
    {
443
        return $this->lastRecordingId;
444
    }
445
446
    public function setLastRecordingId(string $lastRecordingId)
447
    {
448
        $this->lastRecordingId = $lastRecordingId;
449
        Cache::store('openvidu')->update($this->sessionId, $this->toJson());
450
    }
451
452
    /**
453
     * @return string
454
     * @throws OpenViduException
455
     */
456
    public function __toString(): string
457
    {
458
        return $this->getSessionId();
459
    }
460
}
461