001/*
002 *   Copyright 2024 Vonage
003 *
004 *   Licensed under the Apache License, Version 2.0 (the "License");
005 *   you may not use this file except in compliance with the License.
006 *   You may obtain a copy of the License at
007 *
008 *        http://www.apache.org/licenses/LICENSE-2.0
009 *
010 *   Unless required by applicable law or agreed to in writing, software
011 *   distributed under the License is distributed on an "AS IS" BASIS,
012 *   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
013 *   See the License for the specific language governing permissions and
014 *   limitations under the License.
015 */
016package com.vonage.client.video;
017
018import com.vonage.client.DynamicEndpoint;
019import com.vonage.client.HttpWrapper;
020import com.vonage.client.RestEndpoint;
021import com.vonage.client.VonageClient;
022import com.vonage.client.auth.JWTAuthMethod;
023import com.vonage.client.auth.ApiKeyHeaderAuthMethod;
024import com.vonage.client.common.HttpMethod;
025import com.vonage.jwt.Jwt;
026import java.time.ZonedDateTime;
027import java.util.*;
028import java.util.function.Function;
029import java.util.function.Supplier;
030
031/**
032 * A client for using the Vonage Video API. The standard way to obtain an instance of this class is to use
033 * {@link VonageClient#getVideoClient()}.
034 *
035 * @since 8.0.0-beta1
036 */
037public class VideoClient {
038        final Supplier<? extends Jwt.Builder> newJwtSupplier;
039
040        final RestEndpoint<CreateSessionRequest, CreateSessionResponse[]> createSession;
041        final RestEndpoint<String, ListStreamsResponse> listStreams;
042        final RestEndpoint<SessionResourceRequestWrapper, GetStreamResponse> getStream;
043        final RestEndpoint<SetStreamLayoutRequest, Void> setStreamLayout;
044        final RestEndpoint<SignalRequest, Void> signal, signalAll;
045        final RestEndpoint<SessionResourceRequestWrapper, Void> forceDisconnect, muteStream;
046        final RestEndpoint<MuteSessionRequest, ProjectDetails> muteSession;
047        final RestEndpoint<SipDialRequest, SipDialResponse> sipDial;
048        final RestEndpoint<SendDtmfRequest, Void> sendDtmfToSession, sendDtmfToConnection;
049        final RestEndpoint<ListStreamCompositionsRequest, ListArchivesResponse> listArchives;
050        final RestEndpoint<String, Archive> getArchive, stopArchive;
051        final RestEndpoint<Archive, Archive> createArchive;
052        final RestEndpoint<StreamCompositionLayout, Void> updateArchiveLayout, updateBroadcastLayout;
053        final RestEndpoint<PatchComposedStreamsRequest, Void> patchArchiveStream, patchBroadcastStream;
054        final RestEndpoint<String, Void> deleteArchive, stopCaptions, stopRender;
055        final RestEndpoint<ListStreamCompositionsRequest, ListBroadcastsResponse> listBroadcasts;
056        final RestEndpoint<String, Broadcast> getBroadcast, stopBroadcast;
057        final RestEndpoint<Broadcast, Broadcast> createBroadcast;
058        final RestEndpoint<CaptionsRequest, CaptionsResponse> startCaptions;
059        final RestEndpoint<ConnectRequest, ConnectResponse> connect;
060        final RestEndpoint<RenderRequest, RenderResponse> startRender;
061        final RestEndpoint<String, RenderResponse> getRender;
062        final RestEndpoint<ListStreamCompositionsRequest, ListRendersResponse> listRenders;
063
064        /**
065         * Constructor.
066         *
067         * @param wrapper (REQUIRED) shared HTTP wrapper object used for making REST calls.
068         */
069        public VideoClient(HttpWrapper wrapper) {
070                super();
071                Supplier<JWTAuthMethod> jwtAuthGetter = () -> wrapper.getAuthCollection().getAuth(JWTAuthMethod.class);
072                Supplier<String> appIdGetter = () -> jwtAuthGetter.get().getApplicationId();
073                newJwtSupplier = () -> jwtAuthGetter.get().newJwt();
074
075                @SuppressWarnings("unchecked")
076                class Endpoint<T, R> extends DynamicEndpoint<T, R> {
077                        Endpoint(Function<T, String> pathGetter, HttpMethod method, R... type) {
078                                super(DynamicEndpoint.<T, R> builder(type)
079                                                .authMethod(JWTAuthMethod.class, ApiKeyHeaderAuthMethod.class)
080                                                .responseExceptionType(VideoResponseException.class)
081                                                .requestMethod(method).wrapper(wrapper).pathGetter((de, req) -> {
082                                                        String base = de.getHttpWrapper().getHttpConfig().getVideoBaseUri();
083                                                        String end = pathGetter.apply(req);
084                                                        String mid = end.startsWith("/") ? "" : "/v2/project/" + appIdGetter.get() + "/";
085                                                        return base + mid + end;
086                                                })
087                                );
088                        }
089                }
090
091                createSession = new Endpoint<>(req -> "/session/create", HttpMethod.POST);
092                listStreams = new Endpoint<>(req -> "session/"+req+"/stream", HttpMethod.GET);
093                setStreamLayout = new Endpoint<>(req -> "session/"+req.sessionId+"/stream", HttpMethod.PUT);
094                getStream = new Endpoint<>(req -> "session/"+req.sessionId+"/stream/"+req.resourceId, HttpMethod.GET);
095                signalAll = new Endpoint<>(req -> "session/"+req.sessionId+"/signal", HttpMethod.POST);
096                signal = new Endpoint<>(req -> "session/"+req.sessionId+"/connection/"+req.connectionId+"/signal", HttpMethod.POST);
097                forceDisconnect = new Endpoint<>(req -> "session/"+req.sessionId+"/connection/"+req.resourceId, HttpMethod.DELETE);
098                muteStream = new Endpoint<>(req -> "session/"+req.sessionId+"/stream/"+req.resourceId+"/mute", HttpMethod.POST);
099                muteSession = new Endpoint<>(req -> "session/"+req.sessionId+"/mute", HttpMethod.POST);
100                updateArchiveLayout = new Endpoint<>(req -> "archive/"+req.id+"/layout", HttpMethod.PUT);
101                deleteArchive = new Endpoint<>(req -> "archive/"+req, HttpMethod.DELETE);
102                patchArchiveStream = new Endpoint<>(req -> "archive/"+req.id+"/streams", HttpMethod.PATCH);
103                stopArchive = new Endpoint<>(req -> "archive/"+req+"/stop", HttpMethod.POST);
104                createArchive = new Endpoint<>(req -> "archive", HttpMethod.POST);
105                listArchives = new Endpoint<>(req -> "archive", HttpMethod.GET);
106                getArchive = new Endpoint<>(req -> "archive/"+req, HttpMethod.GET);
107                sendDtmfToConnection = new Endpoint<>(req -> "session/"+req.sessionId+"/connection/"+req.connectionId+"/play-dtmf", HttpMethod.POST);
108                sendDtmfToSession = new Endpoint<>(req -> "session/"+req.sessionId+"/play-dtmf", HttpMethod.POST);
109                listBroadcasts = new Endpoint<>(req -> "broadcast", HttpMethod.GET);
110                createBroadcast = new Endpoint<>(req -> "broadcast", HttpMethod.POST);
111                getBroadcast = new Endpoint<>(req -> "broadcast/"+req, HttpMethod.GET);
112                stopBroadcast = new Endpoint<>(req -> "broadcast/"+req+"/stop", HttpMethod.POST);
113                updateBroadcastLayout = new Endpoint<>(req -> "broadcast/"+req.id+"/layout", HttpMethod.PUT);
114                patchBroadcastStream = new Endpoint<>(req -> "broadcast/"+req.id+"/streams", HttpMethod.PATCH);
115                sipDial = new Endpoint<>(req -> "dial", HttpMethod.POST);
116                startCaptions = new Endpoint<>(req -> "captions", HttpMethod.POST);
117                stopCaptions = new Endpoint<>(req -> "captions/"+req+"/stop", HttpMethod.POST);
118                connect = new Endpoint<>(req -> "connect", HttpMethod.POST);
119                startRender = new Endpoint<>(req -> "render", HttpMethod.POST);
120                listRenders = new Endpoint<>(req -> "render", HttpMethod.GET);
121                getRender = new Endpoint<>(req -> "render/"+req, HttpMethod.GET);
122                stopRender = new Endpoint<>(req -> "render/"+req, HttpMethod.DELETE);
123        }
124
125        private String validateId(String param, String name, boolean uuid) {
126                if (param == null || param.isEmpty()) {
127                        throw new IllegalArgumentException(name+" ID is required.");
128                }
129                if (uuid) {
130                        // Validate by construction
131                        Objects.requireNonNull(UUID.fromString(param));
132                }
133                return param;
134        }
135
136        private String validateSessionId(String sessionId) {
137                return validateId(sessionId, "Session", false);
138        }
139
140        private String validateConnectionId(String connectionId) {
141                return validateId(connectionId, "Connection", true);
142        }
143
144        private String validateStreamId(String streamId) {
145                return validateId(streamId, "Stream", true);
146        }
147
148        private String validateArchiveId(String archiveId) {
149                return validateId(archiveId, "Archive", true);
150        }
151
152        private String validateBroadcastId(String broadcastId) {
153                return validateId(broadcastId, "Broadcast", true);
154        }
155
156        private String validateRenderId(String renderId) {
157                return validateId(renderId, "Render", true);
158        }
159
160        private <T> T validateRequest(T request) {
161                if (request == null) {
162                        throw new IllegalArgumentException("Request properties are required.");
163                }
164                return request;
165        }
166
167        /**
168         * Generates a signed JSON Web Token which can be passed to the client.
169         *
170         * @param sessionId The session ID.
171         * @param options Configuration parameters (claims) of the token, e.g. role, expiry time etc.
172         *
173         * @return The JWT with the specified properties, as a raw string.
174         *
175         * @since 8.0.0-beta2
176         */
177        public String generateToken(String sessionId, TokenOptions options) {
178                Jwt.Builder jwtBuilder = newJwtSupplier.get();
179                if (options == null) {
180                        options = TokenOptions.builder().build();
181                }
182                options.addClaims(jwtBuilder);
183                return jwtBuilder
184                                .addClaim("session_id", validateSessionId(sessionId))
185                                .addClaim("scope", "session.connect")
186                                .issuedAt(ZonedDateTime.now())
187                                .build().generate();
188        }
189
190        /**
191         * Generates a signed JSON Web Token which can be passed to the client, using the default token options.
192         *
193         * @param sessionId The session ID.
194         *
195         * @return The JWT with the default properties, as a raw string.
196         *
197         * @see #generateToken(String, TokenOptions)
198         * @since 8.0.0-beta2
199         */
200        public String generateToken(String sessionId) {
201                return generateToken(sessionId, null);
202        }
203
204        /**
205         * Generate a new session, using default properties.
206         *
207         * @return Details of the created session.
208         * @see #createSession(CreateSessionRequest)
209         */
210        public CreateSessionResponse createSession() {
211                return createSession(null);
212        }
213
214        /**
215         * Generate a new session.
216         *
217         * @param request (OPTIONAL) The session properties.
218         * @return Details of the created session.
219         */
220        public CreateSessionResponse createSession(CreateSessionRequest request) {
221                CreateSessionResponse[] response = createSession.execute(request);
222        return response == null || response.length == 0 ? new CreateSessionResponse() : response[0];
223        }
224
225        /**
226         * Use this method to get information on all Vonage Video streams in a session.
227         *
228         * @param sessionId The session ID.
229         * @return Details for each stream, as a List.
230         * @see #getStream(String, String)
231         */
232        public List<GetStreamResponse> listStreams(String sessionId) {
233                return listStreams.execute(validateSessionId(sessionId)).getItems();
234        }
235
236        /**
237         * Use this method to get information on a Vonage Video stream. For example, you can call this method to get
238         * information about layout classes used by a Vonage Video stream. The layout classes define how the stream is
239         * displayed in the layout of a broadcast stream.
240         *
241         * @param sessionId The session ID.
242         * @param streamId ID of the stream to retrieve.
243         * @return Details of the requested stream.
244         */
245        public GetStreamResponse getStream(String sessionId, String streamId) {
246                return getStream.execute(new SessionResourceRequestWrapper(
247                                validateSessionId(sessionId),
248                                validateStreamId(streamId)
249                ));
250        }
251
252        /**
253         * Use this method to change layout classes for a Vonage Video stream. The layout classes define how the stream is
254         * displayed in the layout of a composed Vonage Video archive.
255         *
256         * @param sessionId The session ID.
257         * @param streams The stream layouts to change.
258         */
259        public void setStreamLayout(String sessionId, List<SessionStream> streams) {
260                setStreamLayout.execute(new SetStreamLayoutRequest(validateSessionId(sessionId), streams));
261        }
262
263        /**
264         * Use this method to change layout classes for a Vonage Video stream. The layout classes define how the stream is
265         * displayed in the layout of a composed Vonage Video archive.
266         *
267         * @param sessionId The session ID.
268         * @param streams The stream layouts to change.
269         *
270         * @see #setStreamLayout(String, List)
271         * @since 8.0.0-beta2
272         */
273        public void setStreamLayout(String sessionId, SessionStream... streams) {
274                setStreamLayout(sessionId, Arrays.asList(streams));
275        }
276
277        /**
278         * Sends signals to all participants in an active Vonage Video session.
279         *
280         * @param sessionId The session ID.
281         * @param request Signal payload.
282         */
283        public void signalAll(String sessionId, SignalRequest request) {
284                validateRequest(request);
285                request.sessionId = validateSessionId(sessionId);
286                signalAll.execute(request);
287        }
288
289        /**
290         * Sends signal to a specific participant in an active Vonage Video session.
291         *
292         * @param sessionId The session ID.
293         * @param connectionId Specific publisher connection ID.
294         * @param request Signal payload.
295         */
296        public void signal(String sessionId, String connectionId, SignalRequest request) {
297                validateRequest(request);
298                request.sessionId = validateSessionId(sessionId);
299                request.connectionId = validateConnectionId(connectionId);
300                signal.execute(request);
301        }
302
303        /**
304         * Force a client to disconnect from a session.
305         *
306         * @param sessionId The session ID.
307         * @param connectionId Specific publisher connection ID.
308         */
309        public void forceDisconnect(String sessionId, String connectionId) {
310                forceDisconnect.execute(new SessionResourceRequestWrapper(
311                                validateSessionId(sessionId),
312                                validateConnectionId(connectionId)
313                ));
314        }
315
316        /**
317         * Force mute a specific publisher stream.
318         *
319         * @param sessionId The session ID.
320         * @param streamId ID of the stream to mute.
321         */
322        public void muteStream(String sessionId, String streamId) {
323                muteStream.execute(new SessionResourceRequestWrapper(
324                                validateSessionId(sessionId),
325                                validateStreamId(streamId)
326                ));
327        }
328
329        /**
330         * Force all streams (except for an optional list of streams) in a session to mute published audio.
331         * You can also use this method to disable the force mute state of a session.
332         *
333         * @param sessionId The session ID.
334         *
335         * @param active Whether to mute streams in the session (true) and enable the mute state of the session, or to
336         * disable the mute state of the session (false). With the mute state enabled (true), all current and future
337         * streams published to the session (except streams in "excludedStreamIds") are muted. If this is
338         * set to {@code false}, future streams published to the session are not muted (but any existing muted
339         * streams will remain muted).
340         *
341         * @param excludedStreamIds (OPTIONAL) The stream IDs for streams that should not be muted.
342         * If you omit this, all streams in the session will be muted. This only applies when the "active" property is set
343         * {@code true}. When the "active" property is set to {@code false}, it is ignored.
344         *
345         * @return Details about the Vonage Video project.
346         */
347        public ProjectDetails muteSession(String sessionId, boolean active, Collection<String> excludedStreamIds) {
348                return muteSession.execute(new MuteSessionRequest(
349                                validateSessionId(sessionId),
350                                active,
351                                excludedStreamIds
352                ));
353        }
354
355        /**
356         * Force all streams (except for an optional list of streams) in a session to mute published audio.
357         * You can also use this method to disable the force mute state of a session.
358         *
359         * @param sessionId The session ID.
360         *
361         * @param active Whether to mute streams in the session (true) and enable the mute state of the session, or to
362         * disable the mute state of the session (false). With the mute state enabled (true), all current and future
363         * streams published to the session (except streams in "excludedStreamIds") are muted. If this is
364         * set to {@code false}, future streams published to the session are not muted (but any existing muted
365         * streams will remain muted).
366         *
367         * @param excludedStreamIds (OPTIONAL) The stream IDs for streams that should not be muted.
368         * If you omit this, all streams in the session will be muted. This only applies when the "active" property is set
369         * {@code true}. When the "active" property is set to {@code false}, it is ignored.
370         *
371         * @see #muteSession(String, boolean, Collection)
372         */
373        public void muteSession(String sessionId, boolean active, String... excludedStreamIds) {
374                muteSession(sessionId, active,
375                                excludedStreamIds != null && excludedStreamIds.length > 0 ?
376                                        Arrays.asList(excludedStreamIds) : null
377                );
378        }
379
380        /**
381         * Use this method to connect your SIP platform to a Vonage video session.
382         * The audio from your end of the SIP call is added to the video session as an audio-only stream. The Vonage
383         * Media Router mixes audio from other streams in the session and sends the mixed audio to your SIP endpoint.
384         *
385         * <p>
386         * The call ends when your SIP server sends a BYE message (to terminate the call). You can also end a call
387         * using {@link #forceDisconnect(String, String)}. The Vonage Video SIP gateway automatically ends a call after
388         * 5 minutes of inactivity (5 minutes without media received). Also, as a security measure, the Vonage Video SIP
389         * gateway closes any SIP call that lasts longer than 6 hours.
390         *
391         * <p>
392         * The SIP interconnect feature requires that you use video session that uses the Vonage Media Router
393         * (a session with the media mode set to {@link MediaMode#ROUTED}).
394         *
395         * <p>
396         * For more information, including technical details and security considerations, see the
397         * Vonage SIP interconnect developer guide.
398         *
399         * @param request The outbound SIP call's properties.
400         *
401         * @return Details of the SIP connection.
402         *
403         * @since 8.0.0-beta4
404         */
405        public SipDialResponse sipDial(SipDialRequest request) {
406                return sipDial.execute(validateRequest(request));
407        }
408
409        /**
410         * Play DMTF tones into a specific connection.
411         * Telephony events are negotiated over SDP and transmitted as RFC4733/RFC2833 digits to the remote endpoint.
412         *
413         * @param sessionId The session ID.
414         * @param connectionId Specific publisher connection ID.
415         * @param digits The string of DTMF digits to send. This can include 0-9, '*', '#', and 'p'.
416         * A 'p' indicates a pause of 500ms (if you need to add a delay in sending the digits).
417         *
418         * @since 8.0.0-beta4
419         */
420        public void sendDtmf(String sessionId, String connectionId, String digits) {
421                sendDtmfToConnection.execute(new SendDtmfRequest(
422                                validateSessionId(sessionId),
423                                validateConnectionId(connectionId),
424                                String.valueOf(digits)
425                ));
426        }
427
428        /**
429         * Play DTMF tones into a SIP call.
430         * Telephony events are negotiated over SDP and transmitted as RFC4733/RFC2833 digits to the remote endpoint.
431         *
432         * @param sessionId The session ID.
433         * @param digits The string of DTMF digits to send. This can include 0-9, '*', '#', and 'p'.
434         * A 'p' indicates a pause of 500ms (if you need to add a delay in sending the digits).
435         *
436         * @since 8.0.0-beta4
437         */
438        public void sendDtmf(String sessionId, String digits) {
439                sendDtmfToSession.execute(new SendDtmfRequest(
440                                validateSessionId(sessionId),
441                                null, String.valueOf(digits)
442                ));
443        }
444
445        /**
446         * List all archives in the application. Deleted archives are not included in the results.
447         *
448         * @return The list of archives up to the first 1000, in order from newest to oldest.
449         *
450         * @see #listArchives(ListStreamCompositionsRequest)
451         */
452        public List<Archive> listArchives() {
453                return listArchives(ListStreamCompositionsRequest.maxResults());
454        }
455
456        /**
457         * List all archives in the application. Deleted archives are not included in the results.
458         *
459         * @param request (OPTIONAL) Filter properties of the request.
460         *
461         * @return The list of archives matching the filter criteria, in order from newest to oldest.
462         */
463        public List<Archive> listArchives(ListStreamCompositionsRequest request) {
464                return listArchives.execute(request).getItems();
465        }
466
467        /**
468         * Retrieve information about a specific archive.
469         *
470         * @param archiveId ID of the archive to retrieve.
471         *
472         * @return The Archive corresponding to the archiveId.
473         */
474        public Archive getArchive(String archiveId) {
475                return getArchive.execute(validateArchiveId(archiveId));
476        }
477
478        /**
479         * Create a new archive.
480         *
481         * @param request Properties of the archive.
482         *
483         * @return The created Archive.
484         */
485        public Archive createArchive(Archive request) {
486                return createArchive.execute(validateRequest(request));
487        }
488
489        /**
490         * Dynamically change the layout type of a composed archive while it is being recorded.
491         *
492         * @param archiveId ID of the archive to change.
493         * @param layout Properties of the layout change request.
494         */
495        public void updateArchiveLayout(String archiveId, StreamCompositionLayout layout) {
496                validateRequest(layout);
497                layout.id = validateArchiveId(archiveId);
498                updateArchiveLayout.execute(layout);
499        }
500
501        /**
502         * Adds a stream to a composed archive that was started with the
503         * {@code streamMode} set to {@link StreamMode#MANUAL}.
504         *
505         * @param archiveId ID of the archive.
506         * @param streamId ID of the stream to add.
507         *
508         * @see #addArchiveStream(String, String, Boolean, Boolean)
509         */
510        public void addArchiveStream(String archiveId, String streamId) {
511                addArchiveStream(archiveId, streamId, null, null);
512        }
513
514        /**
515         * Adds a stream to a composed archive.
516         *
517         * @param archiveId ID of the archive.
518         * @param streamId ID of the stream to add.
519         * @param audio (OPTIONAL) Whether the composed archive should include the stream's audio (true by default).
520         * @param video (OPTIONAL) Whether the composed archive should include the stream's video (true by default).
521         */
522        public void addArchiveStream(String archiveId, String streamId, Boolean audio, Boolean video) {
523                patchArchiveStream(archiveId, new PatchComposedStreamsRequest(validateStreamId(streamId), audio, video));
524        }
525
526        /**
527         * Removes a stream from a composed archive that was started with the
528         * {@code streamMode} set to {@link StreamMode#MANUAL}.
529         *
530         * @param archiveId ID of the archive.
531         * @param streamId ID of the stream to remove.
532         */
533        public void removeArchiveStream(String archiveId, String streamId) {
534                patchArchiveStream(archiveId, new PatchComposedStreamsRequest(validateStreamId(streamId)));
535        }
536
537        private void patchArchiveStream(String archiveId, PatchComposedStreamsRequest request) {
538                PatchComposedStreamsRequest pasr = validateRequest(request);
539                pasr.id = validateArchiveId(archiveId);
540                patchArchiveStream.execute(pasr);
541        }
542
543        /**
544         * Archives stop recording after 4 hours (14,400 seconds), or 60 seconds after the last client disconnects from
545         * the session, or 60 minutes after the last client stops publishing. However, automatic archives continue
546         * recording to multiple consecutive files of up to 4 hours in length each.
547         * <p>
548         * Calling this method for automatic archives has no effect. Automatic archives continue recording to multiple
549         * consecutive files of up to 4 hours (14,400 seconds) in length each, until 60 seconds after the last client
550         * disconnects from the session, or 60 minutes after the last client stops publishing a stream to the session.
551         *
552         * @param archiveId ID of the archive to stop.
553         *
554         * @return The Archive corresponding to the archiveId.
555         */
556        public Archive stopArchive(String archiveId) {
557                return stopArchive.execute(validateArchiveId(archiveId));
558        }
559
560        /**
561         * Deletes an archive. You can only delete an archive which has a status of
562         * {@linkplain ArchiveStatus#AVAILABLE} or {@linkplain ArchiveStatus#UPLOADED}.
563         * Deleting an archive removes its record from the list of archives (see {@linkplain #listArchives()}).
564         * For an "available" archive, it also removes the archive file, making it unavailable for download.
565         *
566         * @param archiveId ID of the archive to delete.
567         */
568        public void deleteArchive(String archiveId) {
569                deleteArchive.execute(validateArchiveId(archiveId));
570        }
571
572        /**
573         * List all broadcasts that are in progress and started in the application.
574         * Completed broadcasts are not included in the listing.
575         *
576         * @return The list of broadcasts up to the first 1000, in order from newest to oldest.
577         *
578         * @see #listBroadcasts(ListStreamCompositionsRequest)
579         * @since 8.0.0-beta4
580         */
581        public List<Broadcast> listBroadcasts() {
582                return listBroadcasts(ListStreamCompositionsRequest.maxResults());
583        }
584
585        /**
586         * List all broadcasts that are in progress and started in the application.
587         * Completed broadcasts are not included in the listing.
588         *
589         * @param request (OPTIONAL) Filter properties of the request.
590         *
591         * @return The list of broadcasts matching the filter criteria, in order from newest to oldest.
592         *
593         * @since 8.0.0-beta4
594         */
595        public List<Broadcast> listBroadcasts(ListStreamCompositionsRequest request) {
596                return listBroadcasts.execute(request).getItems();
597        }
598
599        /**
600         * Get Information about a Broadcast that is in progress.
601         *
602         * @param broadcastId ID of the broadcast to retrieve.
603         *
604         * @return The Broadcast corresponding to the broadcastId.
605         *
606         * @since 8.0.0-beta4
607         */
608        public Broadcast getBroadcast(String broadcastId) {
609                return getBroadcast.execute(validateBroadcastId(broadcastId));
610        }
611
612        /**
613         * Start a new live streaming broadcast.
614         * This broadcasts the session to an HLS (HTTP live streaming) or to RTMP streams.
615         * To successfully start broadcasting a session, at least one client must be connected to the session.
616         * <p>
617         * The live streaming broadcast can target one HLS endpoint and up to five RTMP servers simultaneously for
618         * a session. You can only start live streaming for sessions that use the Vonage Media Router (with the
619         * media mode set to {@linkplain MediaMode#ROUTED}); you cannot use live streaming with sessions that have the
620         * media mode set to {@linkplain MediaMode#RELAYED}.
621         *
622         * @param request Broadcast object with initial properties.
623         *
624         * @return The same Broadcast object that was passed in with additional fields populated from the server's response.
625         *
626         * @since 8.0.0-beta4
627         */
628        public Broadcast createBroadcast(Broadcast request) {
629                return createBroadcast.execute(validateRequest(request));
630        }
631
632        /**
633         * Dynamically change the layout type of a live streaming broadcast.
634         *
635         * @param broadcastId ID of the broadcast to change.
636         * @param layout Properties of the layout change request.
637         *
638         * @since 8.0.0-beta4
639         */
640        public void updateBroadcastLayout(String broadcastId, StreamCompositionLayout layout) {
641                validateRequest(layout);
642                layout.id = validateBroadcastId(broadcastId);
643                updateBroadcastLayout.execute(layout);
644        }
645
646        /**
647         * Adds a stream to a live broadcast that was started with the {@code streamMode} set to {@link StreamMode#MANUAL}.
648         *
649         * @param broadcastId ID of the broadcast.
650         * @param streamId ID of the stream to add.
651         *
652         * @see #addBroadcastStream(String, String, Boolean, Boolean)
653         * @since 8.0.0-beta4
654         */
655        public void addBroadcastStream(String broadcastId, String streamId) {
656                addBroadcastStream(broadcastId, streamId, null, null);
657        }
658
659        /**
660         * Adds a stream to a live broadcast that was started with the {@code streamMode} set to {@link StreamMode#MANUAL}.
661         *
662         * @param broadcastId ID of the broadcast.
663         * @param streamId ID of the stream to add.
664         * @param audio (OPTIONAL) Whether the broadcast should include the stream's audio (true by default).
665         * @param video (OPTIONAL) Whether the broadcast should include the stream's video (true by default).
666         *
667         * @since 8.0.0-beta4
668         */
669        public void addBroadcastStream(String broadcastId, String streamId, Boolean audio, Boolean video) {
670                patchBroadcastStream(broadcastId, new PatchComposedStreamsRequest(validateStreamId(streamId), audio, video));
671        }
672
673        /**
674         * Removes a stream from a live broadcast that was started with the
675         * {@code streamMode} set to {@link StreamMode#MANUAL}.
676         *
677         * @param broadcastId ID of the broadcastId.
678         * @param streamId ID of the stream to remove.
679         *
680         * @since 8.0.0-beta4
681         */
682        public void removeBroadcastStream(String broadcastId, String streamId) {
683                patchBroadcastStream(broadcastId, new PatchComposedStreamsRequest(validateStreamId(streamId)));
684        }
685
686        private void patchBroadcastStream(String broadcastId, PatchComposedStreamsRequest request) {
687                PatchComposedStreamsRequest pasr = validateRequest(request);
688                pasr.id = validateBroadcastId(broadcastId);
689                patchBroadcastStream.execute(pasr);
690        }
691
692        /**
693         * Stop a live broadcast.
694         * Note that a broadcast stops automatically 60 seconds after the last client disconnects from the session.
695         * There is a default maximum duration of 4 hours (14400 seconds) for each HLS and RTMP stream (the live
696         * stream broadcast automatically stops when this duration is reached). You can change the maximum duration for
697         * the broadcast by setting the {@link Broadcast.Builder#maxDuration(int)} property when you start the broadcast
698         * using the {@link #createBroadcast(Broadcast)} method.
699         *
700         * @param broadcastId ID of the broadcast to stop.
701         *
702         * @return Details of the Broadcast.
703         *
704         * @since 8.0.0-beta4
705         */
706        public Broadcast stopBroadcast(String broadcastId) {
707                return stopBroadcast.execute(validateBroadcastId(broadcastId));
708        }
709
710        /**
711         * Start real-time Live Captions for a Vonage Video Session.
712         * <p>
713         * The maximum allowed duration is 4 hours, after which the audio captioning will stop without any
714         * effect on the ongoing Vonage Video Session. An event will be posted to your callback URL if provided
715         * when starting the captions. Each Vonage Video Session supports only one audio captioning session.
716         *
717         * @param request Properties of the live captioning.
718         *
719         * @return Live captioning metadata.
720         *
721         * @since 8.5.0
722         */
723        public CaptionsResponse startCaptions(CaptionsRequest request) {
724                return startCaptions.execute(validateRequest(request));
725        }
726
727        /**
728         * Stop live captions for a session.
729         *
730         * @param captionsId ID of the live captions to stop.
731         *
732         * @since 8.5.0
733         */
734        public void stopCaptions(String captionsId) {
735                stopCaptions.execute(validateId(captionsId, "Captions", true));
736        }
737
738        /**
739         * Send audio from a Vonage Video API session to a WebSocket.
740         *
741         * @param request Properties of the WebSocket connection.
742         *
743         * @return The connection metadata.
744         *
745         * @since 8.5.0
746         */
747        public ConnectResponse connectToWebsocket(ConnectRequest request) {
748                return connect.execute(validateRequest(request));
749        }
750
751        /**
752         * List all Experience Composers in the application.
753         *
754         * @return The list of Experience Composers up to the first 1000, in order from newest to oldest.
755         *
756         * @since 8.6.0
757         * @see #listRenders(ListStreamCompositionsRequest)
758         */
759        public List<RenderResponse> listRenders() {
760                return listRenders(ListStreamCompositionsRequest.maxResults());
761        }
762
763        /**
764         * List Experience Composers in the application, with the specified offset and number of results.
765         *
766         * @param request (OPTIONAL) Filter properties of the request.
767         * Note that only {@code offset} and {@code count} are applicable here.
768         *
769         * @return The list of broadcasts matching the filter criteria, in order from newest to oldest.
770         *
771         * @since 8.6.0
772         * @see #listRenders()
773         */
774        public List<RenderResponse> listRenders(ListStreamCompositionsRequest request) {
775                return listRenders.execute(request).getItems();
776        }
777
778        /**
779         * Retrieve details on an Experience Composer.
780         *
781         * @param renderId ID of the Experience Composer instance.
782         *
783         * @return The Experience Composer corresponding to the specified renderId.
784         *
785         * @since 8.6.0
786         */
787        public RenderResponse getRender(String renderId) {
788                return getRender.execute(validateRenderId(renderId));
789        }
790
791        /**
792         * Create an Experience Composer for a Vonage Video session.
793         *
794         * @param request Properties of the Experience Composer.
795         *
796         * @return Details of the created Experience Composer.
797         *
798         * @since 8.6.0
799         */
800        public RenderResponse startRender(RenderRequest request) {
801                return startRender.execute(validateRequest(request));
802        }
803
804        /**
805         * Stop an active Experience Composer.
806         *
807         * @param renderId ID of the Experience Composer instance.
808         *
809         * @since 8.6.0
810         */
811        public void stopRender(String renderId) {
812                stopRender.execute(validateRenderId(renderId));
813        }
814}