Beispiel #1
0
    def perform_update(self, serializer):
        instance = self.get_object()
        super().perform_update(serializer)

        # if the starting_at time has changed, add flag to livesessions that have subscribed
        if (
            serializer.instance.is_scheduled
            and serializer.instance.starting_at != instance.starting_at
        ):
            LiveSession.objects.filter(
                should_send_reminders=True,
                is_registered=True,
                video=serializer.instance,
            ).exclude(  # exclude current ones already tagged
                must_notify__overlap=[
                    settings.REMINDER_DATE_UPDATED,
                ]
            ).exclude(
                reminders__overlap=[
                    settings.REMINDER_DATE_UPDATED,  # exclude current ones getting updated
                    settings.REMINDER_ERROR,
                ]
            ).update(
                must_notify=Func(
                    F("must_notify"),
                    Value(settings.REMINDER_DATE_UPDATED),
                    function="array_append",
                    arity=2,
                )
            )
        channel_layers_utils.dispatch_video_to_groups(serializer.instance)
Beispiel #2
0
    def stop_recording(
        self,
        request,
        pk=None,
    ):
        """Stops video recording.

        Parameters
        ----------
        request : Type[django.http.request.HttpRequest]
            The request on the API endpoint
        pk: string
            The primary key of the video

        Returns
        -------
        Type[rest_framework.response.Response]
            HttpResponse with the serialized video.
        """
        video = self.get_object()
        try:
            stop_recording(video)
        except VideoRecordingError as error:
            return Response({"detail": str(error)}, status=400)

        channel_layers_utils.dispatch_video_to_groups(video)

        serializer = self.get_serializer(video)
        return Response(serializer.data)
Beispiel #3
0
    def end_sharing(
        self,
        request,
        pk=None,
    ):
        """Ends the media live sharing in a live stream.

        Broadcasts a xmpp message to all users in the video room.

        Parameters
        ----------
        request : Type[django.http.request.HttpRequest]
            The request on the API endpoint
        pk: string
            The primary key of the video

        Returns
        -------
        Type[rest_framework.response.Response]
            HttpResponse with the serialized video.
        """
        video = self.get_object()
        if not video.active_shared_live_media:
            return Response({"detail": "No shared live media."}, status=400)

        video.active_shared_live_media = None
        video.active_shared_live_media_page = None
        video.save()

        serializer = self.get_serializer(video)
        channel_layers_utils.dispatch_video_to_groups(video)
        return Response(serializer.data)
Beispiel #4
0
 def destroy(self, request, *args, **kwargs):
     """
     Delete the SharedLiveMedia object.
     If active on a video,
     sets the video's `active_shared_live_media` and `active_shared_live_media_page`
     attributes to None.
     """
     shared_live_media = self.get_object()
     video = shared_live_media.video
     if video.active_shared_live_media == shared_live_media:
         video.active_shared_live_media = None
         video.active_shared_live_media_page = None
         video.save()
     response = super().destroy(request, *args, **kwargs)
     channel_layers_utils.dispatch_video_to_groups(video)
     return response
Beispiel #5
0
    def participants_in_discussion(
        self,
        request,
        pk=None,
    ):
        """Adds and deletes a participant who have joined a live stream.

        Broadcasts a xmpp message to all users in the video room.

        Parameters
        ----------
        request : Type[django.http.request.HttpRequest]
            The request on the API endpoint, it should contain a payload with a participant.
        pk: string
            The primary key of the video

        Returns
        -------
        Type[rest_framework.response.Response]
            HttpResponse with the serialized video.
        """

        participant_serializer = serializers.ParticipantSerializer(data=request.data)

        if not participant_serializer.is_valid():
            return Response(
                participant_serializer.errors, status=status.HTTP_400_BAD_REQUEST
            )

        video = self.get_object()
        try:
            if request.method == "POST":
                move_participant_to_discussion(
                    video, participant_serializer.validated_data
                )
            elif request.method == "DELETE":
                remove_participant_from_discussion(
                    video, participant_serializer.validated_data
                )
        except VideoParticipantsException as error:
            return Response({"detail": str(error)}, status=status.HTTP_400_BAD_REQUEST)

        serializer = self.get_serializer(video)
        channel_layers_utils.dispatch_video_to_groups(video)
        return Response(serializer.data)
Beispiel #6
0
    def navigate_sharing(
        self,
        request,
        pk=None,
    ):
        """Changes page of the media live shared in a live stream.

        Broadcasts a xmpp message to all users in the video room.

        Parameters
        ----------
        request : Type[django.http.request.HttpRequest]
            The request on the API endpoint
        pk: string
            The primary key of the video

        Returns
        -------
        Type[rest_framework.response.Response]
            HttpResponse with the serialized video.
        """
        video = self.get_object()

        if not video.active_shared_live_media:
            return Response({"detail": "No shared live media."}, status=400)

        try:
            target_page = int(request.data.get("target_page"))
        except TypeError:
            return Response({"detail": "Invalid page number."}, status=400)

        if target_page > video.active_shared_live_media.nb_pages:
            return Response({"detail": "Page does not exist."}, status=400)

        video.active_shared_live_media_page = target_page
        video.save()
        serializer = self.get_serializer(video)

        channel_layers_utils.dispatch_video_to_groups(video)
        return Response(serializer.data)
Beispiel #7
0
    def start_sharing(
        self,
        request,
        pk=None,
    ):
        """Starts a media live sharing in a live stream.

        Broadcasts a xmpp message to all users in the video room.

        Parameters
        ----------
        request : Type[django.http.request.HttpRequest]
            The request on the API endpoint
        pk: string
            The primary key of the video

        Returns
        -------
        Type[rest_framework.response.Response]
            HttpResponse with the serialized video.
        """
        video = self.get_object()
        if video.active_shared_live_media_id is not None:
            return Response({"detail": "Video is already sharing."}, status=400)

        sharedlivemedia = get_object_or_404(
            SharedLiveMedia,
            id=request.data.get("sharedlivemedia"),
            video=video,
        )
        if sharedlivemedia.upload_state != defaults.READY:
            return Response({"detail": "Shared live media is not ready."}, status=400)

        video.active_shared_live_media = sharedlivemedia
        video.active_shared_live_media_page = 1
        video.save()
        serializer = self.get_serializer(video)

        channel_layers_utils.dispatch_video_to_groups(video)
        return Response(serializer.data)
Beispiel #8
0
    def stop_live(self, request, pk=None):
        """Stop a medialive channel on AWS.

        Parameters
        ----------
        request : Type[django.http.request.HttpRequest]
            The request on the API endpoint
        pk: string
            The primary key of the video

        Returns
        -------
        Type[rest_framework.response.Response]
            HttpResponse with the serialized video.
        """
        video = self.get_object()

        if video.live_state is None:
            return Response({"error": "Impossible to stop a non live video."}, 400)

        if video.live_state != defaults.RUNNING:
            return Response(
                {
                    "error": f"Impossible to stop live video. Current status is {video.live_state}"
                },
                400,
            )

        stop_live_channel(video.get_medialive_channel().get("id"))

        video.live_state = defaults.STOPPING
        if video.is_recording:
            stop_recording(video)
        video.save()
        channel_layers_utils.dispatch_video_to_groups(video)
        serializer = self.get_serializer(video)

        return Response(serializer.data)
Beispiel #9
0
    def live_to_vod(self, request, pk=None):
        """Transforms an harvested live to a VOD.

        Parameters
        ----------
        request : Type[django.http.request.HttpRequest]
            The request on the API endpoint
        pk: string
            The primary key of the video

        Returns
        -------
        Type[rest_framework.response.Response]
            HttpResponse with the serialized video.
        """
        video = self.get_object()

        if video.live_state != defaults.HARVESTED:
            return Response(
                {
                    "error": (
                        "Live video must be harvested before transforming it to VOD."
                        f" Current status is {video.live_state}"
                    )
                },
                400,
            )

        video.live_state = defaults.ENDED
        video.upload_state = defaults.READY
        video.save()

        channel_layers_utils.dispatch_video_to_groups(video)

        serializer = self.get_serializer(video)
        return Response(serializer.data)
Beispiel #10
0
class VideoViewSet(ObjectPkMixin, viewsets.ModelViewSet):
    """Viewset for the API of the video object."""

    queryset = Video.objects.all()
    serializer_class = serializers.VideoSerializer
    permission_classes = [permissions.NotAllowed]

    def get_permissions(self):
        """
        Manage permissions for built-in DRF methods.

        Default to the actions' self defined permissions if applicable or
        to the ViewSet's default permissions.
        """
        if self.action in ["partial_update", "update"]:
            permission_classes = [
                permissions.IsTokenResourceRouteObject & permissions.IsTokenInstructor
                | permissions.IsTokenResourceRouteObject & permissions.IsTokenAdmin
                | permissions.IsVideoPlaylistAdmin
                | permissions.IsVideoOrganizationAdmin
            ]
        elif self.action in ["retrieve"]:
            permission_classes = [
                permissions.IsTokenResourceRouteObject
                | permissions.IsVideoPlaylistAdmin
                | permissions.IsVideoOrganizationAdmin
            ]
        elif self.action in ["list"]:
            permission_classes = [IsAuthenticated]
        elif self.action in ["create"]:
            permission_classes = [
                permissions.IsParamsPlaylistAdmin
                | permissions.IsParamsPlaylistAdminThroughOrganization
                | (
                    permissions.HasPlaylistToken
                    & (permissions.IsTokenInstructor | permissions.IsTokenAdmin)
                )
            ]
        elif self.action in ["destroy"]:
            permission_classes = [
                permissions.IsVideoPlaylistAdmin | permissions.IsVideoOrganizationAdmin
            ]
        else:
            try:
                permission_classes = (
                    getattr(self, self.action).kwargs.get("permission_classes")
                    if self.action
                    else self.permission_classes
                )
            except AttributeError:
                permission_classes = self.permission_classes
        return [permission() for permission in permission_classes]

    def get_serializer_context(self):
        """Extra context provided to the serializer class."""
        context = super().get_serializer_context()

        user = self.request.user
        if isinstance(user, TokenUser):
            context.update(user.token.payload)
            admin_role_permission = permissions.IsTokenAdmin()
            instructor_role_permission = permissions.IsTokenInstructor()

            context["is_admin"] = admin_role_permission.has_permission(
                request=self.request, view=None
            ) or instructor_role_permission.has_permission(
                request=self.request, view=None
            )

        return context

    def perform_update(self, serializer):
        instance = self.get_object()
        super().perform_update(serializer)

        # if the starting_at time has changed, add flag to livesessions that have subscribed
        if (
            serializer.instance.is_scheduled
            and serializer.instance.starting_at != instance.starting_at
        ):
            LiveSession.objects.filter(
                should_send_reminders=True,
                is_registered=True,
                video=serializer.instance,
            ).exclude(  # exclude current ones already tagged
                must_notify__overlap=[
                    settings.REMINDER_DATE_UPDATED,
                ]
            ).exclude(
                reminders__overlap=[
                    settings.REMINDER_DATE_UPDATED,  # exclude current ones getting updated
                    settings.REMINDER_ERROR,
                ]
            ).update(
                must_notify=Func(
                    F("must_notify"),
                    Value(settings.REMINDER_DATE_UPDATED),
                    function="array_append",
                    arity=2,
                )
            )
        channel_layers_utils.dispatch_video_to_groups(serializer.instance)

    def create(self, request, *args, **kwargs):
        """Create one video based on the request payload."""
        try:
            form = forms.VideoForm(request.data)
            video = form.save()
        except ValueError:
            return Response({"errors": [dict(form.errors)]}, status=400)

        serializer = self.get_serializer(video)

        return Response(serializer.data, status=201)

    def list(self, request, *args, **kwargs):
        """List videos through the API."""
        # Limit the queryset to the playlists the user has access directly or through
        # an access they have to an organization
        queryset = self.get_queryset().filter(
            Q(playlist__user_accesses__user__id=request.user.id)
            | Q(playlist__organization__user_accesses__user__id=request.user.id)
        )

        playlist = request.query_params.get("playlist")
        if playlist is not None:
            queryset = queryset.filter(playlist__id=playlist)

        organization = request.query_params.get("organization")
        if organization is not None:
            queryset = queryset.filter(playlist__organization__id=organization)

        queryset = queryset.order_by("title")

        page = self.paginate_queryset(queryset)
        if page is not None:
            serializer = self.get_serializer(page, many=True)
            return self.get_paginated_response(serializer.data)

        serializer = self.get_serializer(queryset, many=True)
        return Response(serializer.data)

    @action(
        methods=["post"],
        detail=True,
        url_path="initiate-upload",
        permission_classes=[
            permissions.IsTokenResourceRouteObject & permissions.IsTokenInstructor
            | permissions.IsTokenResourceRouteObject & permissions.IsTokenAdmin
            | permissions.IsVideoPlaylistAdmin
            | permissions.IsVideoOrganizationAdmin
        ],
    )
    # pylint: disable=unused-argument
    def initiate_upload(self, request, pk=None):
        """Get an upload policy for a video.

        Calling the endpoint resets the upload state to `pending` and returns an upload policy to
        selected video backend.

        Parameters
        ----------
        request : Type[django.http.request.HttpRequest]
            The request on the API endpoint
        pk: string
            The primary key of the video

        Returns
        -------
        Type[rest_framework.response.Response]
            HttpResponse carrying the upload policy as a JSON object.

        """
        response = storage.get_initiate_backend().initiate_video_upload(request, pk)

        # Reset the upload state of the video
        Video.objects.filter(pk=pk).update(upload_state=defaults.PENDING)

        return Response(response)

    @action(
        methods=["post"],
        detail=True,
        url_path="initiate-live",
        permission_classes=[
            permissions.IsTokenResourceRouteObject & permissions.IsTokenInstructor
            | permissions.IsTokenResourceRouteObject & permissions.IsTokenAdmin
            | permissions.HasPlaylistToken
        ],
    )
    # pylint: disable=unused-argument
    def initiate_live(
        self,
        request,
        pk=None,
    ):
        """Create a live stack on AWS ready to stream.

        Parameters
        ----------
        request : Type[django.http.request.HttpRequest]
            The request on the API endpoint
        pk: string
            The primary key of the video

        Returns
        -------
        Type[rest_framework.response.Response]
            HttpResponse with the serialized video.
        """
        serializer = serializers.InitLiveStateSerializer(data=request.data)

        if not serializer.is_valid():
            return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)

        video = self.get_object()

        video.live_type = serializer.validated_data["type"]
        video.upload_state = defaults.PENDING
        video.live_state = defaults.IDLE
        video.uploaded_on = None

        video.save()
        # Dispatch only for admin users. No student should be connected on the video at this point.
        channel_layers_utils.dispatch_video(video, to_admin=True)
        serializer = self.get_serializer(video)

        return Response(serializer.data)

    @action(
        methods=["post"],
        detail=True,
        url_path="start-live",
        permission_classes=[
            permissions.IsTokenResourceRouteObject & permissions.IsTokenInstructor
            | permissions.IsTokenResourceRouteObject & permissions.IsTokenAdmin
        ],
    )
    # pylint: disable=unused-argument
    def start_live(self, request, pk=None):
        """Start a medialive channel on AWS.

        Parameters
        ----------
        request : Type[django.http.request.HttpRequest]
            The request on the API endpoint
        pk: string
            The primary key of the video

        Returns
        -------
        Type[rest_framework.response.Response]
            HttpResponse with the serialized video.
        """
        video = self.get_object()

        if video.live_state is None:
            return Response({"error": "Call initiate-live before starting a live"}, 400)

        if video.live_state not in [
            defaults.IDLE,
            defaults.STOPPED,
            defaults.HARVESTED,
        ]:
            return Response(
                {
                    "error": (
                        f"Impossible to start live video. Current status is {video.live_state}"
                    )
                },
                400,
            )

        # dispatch earlier in the websocket that the live is starting without saving it
        tmp_video = deepcopy(video)
        tmp_video.live_state = defaults.STARTING
        if settings.LIVE_CHAT_ENABLED:
            create_room(video.id)
        channel_layers_utils.dispatch_video_to_groups(tmp_video)
        del tmp_video

        if video.live_info is None or video.live_state == defaults.HARVESTED:
            now = timezone.now()
            stamp = to_timestamp(now)
            key = f"{video.pk}_{stamp}"
            video.live_info = create_live_stream(key)
            wait_medialive_channel_is_created(video.get_medialive_channel().get("id"))

        start_live_channel(video.get_medialive_channel().get("id"))

        # if the video has been harvested, we need to reset its recordings
        # and its related live attendances
        if video.live_state == defaults.HARVESTED:
            video.recording_slices = []
            LiveSession.objects.filter(video=video).update(live_attendance=None)

        video.live_state = defaults.STARTING
        video.upload_state = defaults.PENDING
        video.resolutions = None
        video.save()

        channel_layers_utils.dispatch_video_to_groups(video)

        serializer = self.get_serializer(video)
        return Response(serializer.data)

    @action(
        methods=["post"],
        detail=True,
        url_path="stop-live",
        permission_classes=[
            permissions.IsTokenResourceRouteObject & permissions.IsTokenInstructor
            | permissions.IsTokenResourceRouteObject & permissions.IsTokenAdmin
        ],
    )
    # pylint: disable=unused-argument
    def stop_live(self, request, pk=None):
        """Stop a medialive channel on AWS.

        Parameters
        ----------
        request : Type[django.http.request.HttpRequest]
            The request on the API endpoint
        pk: string
            The primary key of the video

        Returns
        -------
        Type[rest_framework.response.Response]
            HttpResponse with the serialized video.
        """
        video = self.get_object()

        if video.live_state is None:
            return Response({"error": "Impossible to stop a non live video."}, 400)

        if video.live_state != defaults.RUNNING:
            return Response(
                {
                    "error": f"Impossible to stop live video. Current status is {video.live_state}"
                },
                400,
            )

        stop_live_channel(video.get_medialive_channel().get("id"))

        video.live_state = defaults.STOPPING
        if video.is_recording:
            stop_recording(video)
        video.save()
        channel_layers_utils.dispatch_video_to_groups(video)
        serializer = self.get_serializer(video)

        return Response(serializer.data)

    @action(
        methods=["post"],
        detail=True,
        url_path="harvest-live",
        permission_classes=[
            permissions.IsTokenResourceRouteObject & permissions.IsTokenInstructor
            | permissions.IsTokenResourceRouteObject & permissions.IsTokenAdmin
        ],
    )
    # pylint: disable=unused-argument
    def harvest_live(self, request, pk=None):
        """harvest a medialive channel on AWS and delete its stack.

        Parameters
        ----------
        request : Type[django.http.request.HttpRequest]
            The request on the API endpoint
        pk: string
            The primary key of the video

        Returns
        -------
        Type[rest_framework.response.Response]
            HttpResponse with the serialized video.
        """
        video = self.get_object()

        if video.live_state is None:
            return Response({"error": "Impossible to harvest a non live video."}, 400)

        if video.live_state != defaults.STOPPED:
            return Response(
                {
                    "error": (
                        "Live video must be stopped before harvesting it."
                        f" Current status is {video.live_state}"
                    )
                },
                400,
            )

        if not video.recording_slices:
            return Response(
                {"error": "Live video must be recording before harvesting it."}, 400
            )

        # dispatch earlier in the websocket that the live is harvesting without saving it
        tmp_video = deepcopy(video)
        tmp_video.live_state = defaults.HARVESTING
        channel_layers_utils.dispatch_video_to_groups(tmp_video)
        del tmp_video

        if video.is_recording:
            stop_recording(video)

        delete_aws_element_stack(video)
        if settings.LIVE_CHAT_ENABLED:
            close_room(video.id)

        try:
            create_mediapackage_harvest_job(video)
            video.live_state = defaults.HARVESTING
        except ManifestMissingException:
            delete_mediapackage_channel(video.get_mediapackage_channel().get("id"))
            video.upload_state = defaults.PENDING
            video.live_state = defaults.STOPPED
            video.live_info = {
                "started_at": video.live_info.get("started_at"),
                "stopped_at": video.live_info.get("stopped_at"),
            }

        video.save()
        channel_layers_utils.dispatch_video_to_groups(video)
        serializer = self.get_serializer(video)

        return Response(serializer.data)

    @action(
        methods=["post"],
        detail=True,
        url_path="live-to-vod",
        permission_classes=[
            permissions.IsTokenResourceRouteObject & permissions.IsTokenInstructor
            | permissions.IsTokenResourceRouteObject & permissions.IsTokenAdmin
        ],
    )
    # pylint: disable=unused-argument
    def live_to_vod(self, request, pk=None):
        """Transforms an harvested live to a VOD.

        Parameters
        ----------
        request : Type[django.http.request.HttpRequest]
            The request on the API endpoint
        pk: string
            The primary key of the video

        Returns
        -------
        Type[rest_framework.response.Response]
            HttpResponse with the serialized video.
        """
        video = self.get_object()

        if video.live_state != defaults.HARVESTED:
            return Response(
                {
                    "error": (
                        "Live video must be harvested before transforming it to VOD."
                        f" Current status is {video.live_state}"
                    )
                },
                400,
            )

        video.live_state = defaults.ENDED
        video.upload_state = defaults.READY
        video.save()

        channel_layers_utils.dispatch_video_to_groups(video)

        serializer = self.get_serializer(video)
        return Response(serializer.data)

    @action(
        methods=["patch"],
        detail=True,
        url_path="update-live-state",
        permission_classes=[],
    )
    # pylint: disable=unused-argument
    def update_live_state(
        self,
        request,
        pk=None,
    ):
        """View handling AWS POST request to update the video live state.

        Parameters
        ----------
        request : Type[django.http.request.HttpRequest]
            The request on the API endpoint, it should contain a payload with the following fields:
                - state: state of the live, should be "live" or "stopped",
        pk: string
            The primary key of the video

        Returns
        -------
        Type[rest_framework.response.Response]
            HttpResponse acknowledging the success or failure of the live state update operation.

        """
        now = timezone.now()
        stamp = to_timestamp(now)
        msg = request.body
        serializer = serializers.UpdateLiveStateSerializer(data=request.data)

        if serializer.is_valid() is not True:
            return Response(serializer.errors, status=400)

        # Check if the provided signature is valid against any secret in our list
        if not validate_signature(request.headers.get("X-Marsha-Signature"), msg):
            return Response("Forbidden", status=403)

        with transaction.atomic():
            try:
                video = Video.objects.select_for_update(nowait=True).get(pk=pk)
            except Video.DoesNotExist as video_does_not_exists:
                raise Http404 from video_does_not_exists
            except OperationalError:
                return Response({"success": True})

            request_ids = video.live_info.get("medialive", {}).get("request_ids", [])
            if serializer.validated_data["requestId"] in request_ids:
                return Response({"success": True})

            request_ids.append(serializer.validated_data["requestId"])
            video.live_info["medialive"].update({"request_ids": request_ids})
            video.save()

        live_info = video.live_info
        live_info.update(
            {"cloudwatch": {"logGroupName": serializer.validated_data["logGroupName"]}}
        )
        if serializer.validated_data["state"] == defaults.RUNNING:
            video.live_state = defaults.RUNNING
            live_info.update({"started_at": stamp})
            live_info.pop("stopped_at", None)

            # if keys with no timeout were cached for this video, it needs to be reinitialized
            key_cache_video = f"{defaults.VIDEO_ATTENDANCE_KEY_CACHE}{video.id}"
            if list_keys := cache.get(key_cache_video, None):
                cache.delete_many(list_keys)
                cache.delete(key_cache_video)

        if serializer.validated_data["state"] == defaults.STOPPED:
            video.live_state = defaults.STOPPED
            live_info.update({"stopped_at": stamp})
            video.live_info = live_info

        if serializer.validated_data["state"] == defaults.HARVESTED:
            video.live_state = defaults.HARVESTED
            video.resolutions = serializer.validated_data["extraParameters"].get(
                "resolutions"
            )
            live_info = {
                "started_at": video.live_info.get("started_at"),
                "stopped_at": video.live_info.get("stopped_at"),
            }
            video.uploaded_on = time_utils.to_datetime(
                serializer.validated_data["extraParameters"].get("uploaded_on")
            )

        video.live_info = live_info
        video.save()

        channel_layers_utils.dispatch_video_to_groups(video)

        return Response({"success": True})
Beispiel #11
0
    def harvest_live(self, request, pk=None):
        """harvest a medialive channel on AWS and delete its stack.

        Parameters
        ----------
        request : Type[django.http.request.HttpRequest]
            The request on the API endpoint
        pk: string
            The primary key of the video

        Returns
        -------
        Type[rest_framework.response.Response]
            HttpResponse with the serialized video.
        """
        video = self.get_object()

        if video.live_state is None:
            return Response({"error": "Impossible to harvest a non live video."}, 400)

        if video.live_state != defaults.STOPPED:
            return Response(
                {
                    "error": (
                        "Live video must be stopped before harvesting it."
                        f" Current status is {video.live_state}"
                    )
                },
                400,
            )

        if not video.recording_slices:
            return Response(
                {"error": "Live video must be recording before harvesting it."}, 400
            )

        # dispatch earlier in the websocket that the live is harvesting without saving it
        tmp_video = deepcopy(video)
        tmp_video.live_state = defaults.HARVESTING
        channel_layers_utils.dispatch_video_to_groups(tmp_video)
        del tmp_video

        if video.is_recording:
            stop_recording(video)

        delete_aws_element_stack(video)
        if settings.LIVE_CHAT_ENABLED:
            close_room(video.id)

        try:
            create_mediapackage_harvest_job(video)
            video.live_state = defaults.HARVESTING
        except ManifestMissingException:
            delete_mediapackage_channel(video.get_mediapackage_channel().get("id"))
            video.upload_state = defaults.PENDING
            video.live_state = defaults.STOPPED
            video.live_info = {
                "started_at": video.live_info.get("started_at"),
                "stopped_at": video.live_info.get("stopped_at"),
            }

        video.save()
        channel_layers_utils.dispatch_video_to_groups(video)
        serializer = self.get_serializer(video)

        return Response(serializer.data)
Beispiel #12
0
    def start_live(self, request, pk=None):
        """Start a medialive channel on AWS.

        Parameters
        ----------
        request : Type[django.http.request.HttpRequest]
            The request on the API endpoint
        pk: string
            The primary key of the video

        Returns
        -------
        Type[rest_framework.response.Response]
            HttpResponse with the serialized video.
        """
        video = self.get_object()

        if video.live_state is None:
            return Response({"error": "Call initiate-live before starting a live"}, 400)

        if video.live_state not in [
            defaults.IDLE,
            defaults.STOPPED,
            defaults.HARVESTED,
        ]:
            return Response(
                {
                    "error": (
                        f"Impossible to start live video. Current status is {video.live_state}"
                    )
                },
                400,
            )

        # dispatch earlier in the websocket that the live is starting without saving it
        tmp_video = deepcopy(video)
        tmp_video.live_state = defaults.STARTING
        if settings.LIVE_CHAT_ENABLED:
            create_room(video.id)
        channel_layers_utils.dispatch_video_to_groups(tmp_video)
        del tmp_video

        if video.live_info is None or video.live_state == defaults.HARVESTED:
            now = timezone.now()
            stamp = to_timestamp(now)
            key = f"{video.pk}_{stamp}"
            video.live_info = create_live_stream(key)
            wait_medialive_channel_is_created(video.get_medialive_channel().get("id"))

        start_live_channel(video.get_medialive_channel().get("id"))

        # if the video has been harvested, we need to reset its recordings
        # and its related live attendances
        if video.live_state == defaults.HARVESTED:
            video.recording_slices = []
            LiveSession.objects.filter(video=video).update(live_attendance=None)

        video.live_state = defaults.STARTING
        video.upload_state = defaults.PENDING
        video.resolutions = None
        video.save()

        channel_layers_utils.dispatch_video_to_groups(video)

        serializer = self.get_serializer(video)
        return Response(serializer.data)